рдШрдЯрдХ рдХреЛрдб рдореЗрдВ рд╕рджрд╕реНрдпрддрд╛ рдЗрд╕ рдХрд╛рд░реНрдп рдХреЛ AsyncPipe рдореЗрдВ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рд╕реЗ рдмрдЪрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣ рд╣рдореЗрд╢рд╛ рд╕рдВрднрд╡ рдирд╣реАрдВ рд╣реИред рд╕рджрд╕реНрдпрддрд╛ рд╕рдорд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рдЕрд▓рдЧ-рдЕрд▓рдЧ рддрд░реАрдХреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╡реЗ рд╕рднреА рджреЛ рд╕реЗ рдиреАрдЪреЗ рдЖрддреЗ рд╣реИрдВ - рдореИрдиреБрдЕрд▓ рдЕрдирд╕рдмрд╕реНрдХреНрд░рд╛рдЗрдм рдпрд╛ рдЯреЗрдХ рдпреВрдЯрд┐рд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ред
рд╕рдордп рдХреЗ рд╕рд╛рде, рдореИрдВрдиреЗ рддреЗрдЬреА рд╕реЗ рд╕рджрд╕реНрдпрддрд╛ рд╕рдорд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрдкрдиреЗ рдбреЗрдХреЛрд░реЗрдЯрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджрд┐рдпрд╛ред рдЖрдЗрдП рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ рдХрд┐ рдпрд╣ рдХреИрд╕реЗ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдФрд░ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд╢рд╛рдпрдж рдЖрдкрдХреЛ рдпрд╣ рддрд░реАрдХрд╛ рдкрд╕рдВрдж рдЖрдПрдЧрд╛ред
рдореВрд▓ рд╡рд┐рдЪрд╛рд░ рдпрд╣ рд╣реИ рдХрд┐ рдХрд┐рд╕реА рднреА рд╕рджрд╕реНрдпрддрд╛ рдХреЛ рдПрдХ рд╡рд┐рдзрд┐ рд╕реЗ рд╡рд╛рдкрд╕ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдЙрдиред рд╕рднреА рд╕рджрд╕реНрдпрддрд╛рдПрдБ рдПрдХ рдЕрд▓рдЧ рд╕рдЬреА рд╣реБрдИ рд╡рд┐рдзрд┐ рдореЗрдВ рд╣реЛрддреА рд╣реИрдВ рдФрд░ рдЗрд╕рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рд╣реЛрддреЗ рд╣реИрдВред
(...args: any[]) => Subscription;
рдЯрд╛рдЗрдкрд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЖрдкрдХреЛ рдПрдХ рд╡рд┐рдзрд┐ рдкрд░ рдПрдХ рдбреЗрдХреЛрд░реЗрдЯрд░ рд▓рдЯрдХрд╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ рдЬреЛ рд╡рд┐рдзрд┐ рдФрд░ рдЙрд╕рдХреЗ рдкрд░рд┐рдгрд╛рдо рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░ рд╕рдХрддрд╛ рд╣реИред
рддреАрди рдСрдкрд░реЗрд╢рди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреАред
- 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 .
.
GitHub
рдкрд░ рдХреЛрдгреАрдп 9 рдХреЗ рд▓рд┐рдП рд╕реНрдЯреИрдХрдмреНрд▓рд┐рдЯреНрдЬрд╝ рд▓рд┐рдВрдХ
рдбреЗрдХреЛрд░реЗрдЯрд░ рдПрдХ рдкреИрдХреЗрдЬ рдХреЗ рд░реВрдк рдореЗрдВ NPM рдореЗрдВ рд▓рд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ngx-until-on-destroy
рдкрд░ рдбреЗрдХреЛрд░реЗрдЯрд░ рд╕реВрддреНрд░реЛрдВ Github