Angular: cara lain untuk berhenti berlangganan

Langganan dalam kode komponen harus dihindari dengan mengalihkan tugas ini ke AsyncPipe, namun ini tidak selalu memungkinkan. Ada berbagai cara untuk mengakhiri langganan, tetapi semuanya menjadi dua - manual berhenti berlangganan atau menggunakan takeUntil.


Seiring waktu, saya semakin mulai menggunakan dekorator saya untuk berhenti berlangganan. Mari kita pertimbangkan bagaimana ini diatur dan diterapkan, mungkin Anda akan menyukai metode ini.


Ide dasarnya adalah bahwa setiap langganan harus dikembalikan dari suatu metode. Itu semua langganan terjadi dalam metode yang didekorasi secara terpisah dan memiliki tanda tangan berikut.


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

Script memungkinkan Anda untuk menggantung dekorator pada metode yang dapat memodifikasi metode dan hasilnya.


Tiga operasi akan diperlukan.


  1. Saat memanggil ngOnInit, beberapa jenis repositori berlangganan harus dibuat.
  2. Saat memanggil metode dekoratif yang mengembalikan langganan, langganan ini harus disimpan dalam repositori.
  3. Ketika ngOnDestroy dipanggil, semua langganan dari repositori harus diselesaikan (berhenti berlangganan).

Biarkan saya mengingatkan Anda bagaimana dekorator metode kelas dibuat. Dokumentasi resmi ada di sini


Berikut adalah tanda tangan dekorator:


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

Dekorator menerima konstruktor kelas, nama properti, dan deskripsi. Saya akan mengabaikan deskriptor, tidak memainkan peran apa pun untuk tugas ini, hampir tidak ada yang akan memodifikasi deskriptor metode.


, , - , , .


,


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 Link
Untuk Angular 9 Pada GitHub


Penghias dapat diambil dalam NPM sebagai paket ngx-until-on-destroy
sumber Penghias di Github


All Articles