الزاوي: طريقة أخرى لإلغاء الاشتراك

يجب تجنب الاشتراكات في رمز المكون من خلال تحويل هذه المهمة إلى AsyncPipe ، ولكن هذا ليس ممكنًا دائمًا. هناك طرق مختلفة لإنهاء الاشتراكات ، ولكن جميعها تنقسم إلى طريقتين - إلغاء الاشتراك يدويًا أو استخدام takeUntil.


بمرور الوقت ، بدأت بشكل متزايد في استخدام الديكور الخاص بي لإلغاء الاشتراك. دعونا نفكر في كيفية ترتيبها وتطبيقها ، ربما ستعجبك هذه الطريقة.


الفكرة الأساسية هي أنه يجب إرجاع أي اشتراك من طريقة. أولئك. تحدث جميع الاشتراكات بطريقة مزخرفة منفصلة ولديها التوقيع التالي.


(...args: any[]) => Subscription;

يتيح لك Typescript تعليق مصمم على طريقة يمكنها تعديل الطريقة ونتائجها.


ستكون هناك حاجة لثلاث عمليات.


  1. عند استدعاء ngOnInit ، يجب إنشاء نوع من مستودع الاشتراك.
  2. عند استدعاء طريقة مزخرفة تُرجع اشتراكًا ، يجب تخزين هذا الاشتراك في المستودع.
  3. عند استدعاء ngOnDestroy ، يجب إكمال جميع الاشتراكات من المستودع (إلغاء الاشتراك).

اسمحوا لي أن أذكرك بكيفية عمل مصمم أسلوب الطبقة. الوثائق الرسمية هنا


هنا توقيع الديكور:


<T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; 

يستقبل عامل الديكور مُنشئ الصف واسم العقار وواصف كمدخل. سوف أتجاهل الواصف ، فهو لا يلعب أي دور لهذه المهمة ، ولا يكاد أي شخص سيعدل طريقة الواصف.


, , - , , .


,


export function UntilOnDestroy<ClassType extends DirectiveWithSubscription>(): MethodDecorator {
  return function UntilOnDestroyDecorator(target: ClassType, propertyKey: string): TypedPropertyDescriptor<SubscriptionMethod> {
    wrapHooks(target);
    return {
      value: createMethodWrapper(target, target[propertyKey]),
    };
  } as MethodDecorator;
}

, , , — - , , createMethodWrapper.


, .1 . 3 (Subscription). add, unsubscribe .


Subscription


-, .


const subSymbol = Symbol('until-on-destroy');

interface ClassWithSubscription {
  [subSymbol]?: Subscription;
}

, .


createMethodWrapper


2.


function createMethodWrapper(target: ClassWithSubscription, originalMethod: SubscriptionMethod): SubscriptionMethod {
  return function(...args: any[]) {
    const sub: Subscription = originalMethod.apply(this, args);
    target[subSymbol].add(sub);
    return sub;
  };
}

createMethodWrapper , , () . subSymbol, , .


wrapHooks


1 3.


function wrapHooks(target: ClassWithSubscription) {
  if (!target.hasOwnProperty(subSymbol)) {
    target[subSymbol] = null;
    wrapOneHook(target, 'OnInit', t => t[subSymbol] = new Subscription());
    wrapOneHook(target, 'OnDestroy', t => t[subSymbol].unsubscribe());
  }
}

, , subSymbol.


. , , .


. , Angular 9 , . ViewEngine Ivy


const cmpKey = 'ɵcmp';

function wrapOneHook(target: any, hookName: string, wrappingFn: (target: ClassWithSubscription) => void): void {
  return target.constructor[cmpKey]
    ? wrapOneHook__Ivy(target, hookName, wrappingFn)
    : wrapOneHook__ViewEngine(target, hookName, wrappingFn);
}

'ɵcmp' , Ivy . Ivy .


ViewEngine


function wrapOneHook__ViewEngine(target: any, hookName: string, wrappingFn: (target: ClassWithSubscription) => void): void {
  const veHookName = 'ng' + hookName;
  if (!target[veHookName]) {
    throw new Error(`You have to implements ${veHookName} in component ${target.constructor.name}`);
  }
  const originalHook: () => void = target[veHookName];
  target[veHookName] = function (): void {
    wrappingFn(target);
    originalHook.call(this);
  };
}

, ( ), .


wrappingFn.


Ivy , Ivy. , .


ngOnInit ngOnDestroy.


function wrapOneHook__Ivy(target: any, hookName: string, wrappingFn: (target: ClassWithSubscription) => void): void {
  const ivyHookName = hookName.slice(0, 1).toLowerCase() + hookName.slice(1);
  const componentDef: any = target.constructor[cmpKey];

  const originalHook: () => void = componentDef[ivyHookName];
  componentDef[ivyHookName] = function (): void {
    wrappingFn(target);

    if (originalHook) {
      originalHook.call(this);
    }
  };
}

, , componentDef.


, , OnInit ngOnInit, onInit.


, .




ng new check


ng g c child


app.component.ts


@Component({
  selector: 'app-root',
  template: `
    <button #b (click)="b.toggle = !b.toggle">toggle</button>
    <app-child *ngIf="b.toggle"></app-child>
  `,
})
export class AppComponent {}

child.component.ts


@Component({
  selector: 'app-child',
  template: `<p>child: {{id}}</p>`,
})
export class ChildComponent implements OnInit {
  id: string;

  ngOnInit() {
    this.id = Math.random().toString().slice(-3);
    this.sub1();
    this.sub2();
  }

  @UntilOnDestroy()
  sub1(): Subscription {
    console.log(this.id, 'sub1 subscribe');
    return NEVER.pipe(
      finalize(() => console.log(this.id, 'sub1 unsubscribe'))
    )
      .subscribe();
  }

  sub2(): Subscription {
    console.log(this.id, 'sub2 subscribe');
    return NEVER.pipe(
      finalize(() => console.log(this.id, 'sub2 unsubscribe'))
    )
      .subscribe();
  }
}

toggle app-child :



… :



sub1 , sub2 .


.


رابط Stackblitz للزاوية
9 على جيثب


الديكور يمكن أن تؤخذ في npm كمجموعة ngx-until-on-destroy
مصادر الديكور على جيثب


All Articles