يجب تجنب الاشتراكات في رمز المكون من خلال تحويل هذه المهمة إلى AsyncPipe ، ولكن هذا ليس ممكنًا دائمًا. هناك طرق مختلفة لإنهاء الاشتراكات ، ولكن جميعها تنقسم إلى طريقتين - إلغاء الاشتراك يدويًا أو استخدام takeUntil.
بمرور الوقت ، بدأت بشكل متزايد في استخدام الديكور الخاص بي لإلغاء الاشتراك. دعونا نفكر في كيفية ترتيبها وتطبيقها ، ربما ستعجبك هذه الطريقة.
الفكرة الأساسية هي أنه يجب إرجاع أي اشتراك من طريقة. أولئك. تحدث جميع الاشتراكات بطريقة مزخرفة منفصلة ولديها التوقيع التالي.
(...args: any[]) => Subscription;
يتيح لك Typescript تعليق مصمم على طريقة يمكنها تعديل الطريقة ونتائجها.
ستكون هناك حاجة لثلاث عمليات.
- عند استدعاء ngOnInit ، يجب إنشاء نوع من مستودع الاشتراك.
- عند استدعاء طريقة مزخرفة تُرجع اشتراكًا ، يجب تخزين هذا الاشتراك في المستودع.
- عند استدعاء 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
مصادر الديكور على جيثب