In an Angular
app, we're using a Base Component
to unsubscribe from most of our app's observable subscriptions. If a component subscribes to an observable, that component will extend
the Base Component
. My thought is that this keeps observable subscriptions alive until the entire application is destroyed
, rather than until each component is destroyed:
base.component.ts:
import { Subject } from 'rxjs';
import { OnDestroy, Component } from '@angular/core';
export abstract class BaseComponent implements OnDestroy {
protected unsubscribe$ = new Subject<void>();
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
the-rest-of-our-components.ts:
import { Component, OnInit } from '@angular/core';
import { MyService } from 'src/app/services/my.service';
import { BaseComponent } from '../base/component/base-component';
export class myComponent extends BaseComponent implements OnInit {
myProperty: string;
constructor(
private myService: MyService,
) {
super();
}
ngOnInit(): void {
this.myService.doStuff$
.pipe(takeUntil(this.unsubscribe$)) // take until baseComponent's unsubscribe$
.subscribe((data) => {
this.myProperty = data;
});
}
If many components extend BaseComponent
and utilize its unsubscribe$
Subject, does that mean all of my subscriptions only get unsubscribed when the entire application is destroyed (aka user closes the tab or navigates away from web app, thus Base Component
is destroyed), rather than when individual components get destroyed?
Is this a strategy you've seen before, and is it advisable? If it works as I'm assuming, this means all of our subscriptions across the application stay active until the whole app is destroyed. I see how, depending on our needs, that might be a bad thing or a good thing.
Bonus question: is Base Component
going to act like a singleton
? AKA if multiple components simultaneously extend
BaseComponent
, will they all be using the same instance of unsubscribe$
or will there be multiple instances of unsubscribe$
(one per component)?
CodePudding user response:
I assumed this would work, but we all know where assumptions get you, so I made a test: https://stackblitz.com/edit/angular-ivy-ueshwz?file=src/app/extended/extended.component.ts
It works, as in subscriptions get destroyed when individual components get destroyed.
We make a service that holds a subject we can subscribe to, and a value we can change with the subscription, to show that the subscription exists:
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/internal/Subject';
@Injectable({ providedIn: 'root' })
export class UpdateService {
subject = new Subject<void>();
value = 0;
}
In the root we'll fire the subject every second, and have a component that we can toggle on and off
export class AppComponent implements OnInit {
extCompOpen = true;
constructor(public update: UpdateService) {}
ngOnInit() {
interval(1000).subscribe(() => this.update.subject.next());
}
}
<app-extended *ngIf="extCompOpen"></app-extended>
<button (click)="extCompOpen = !extCompOpen">Toggle Component</button>
<p>This counter will keep going up as long as the subscription exists:</p>
<p>{{ update.value }}</p>
Then we'll use an extended component to tick that value up by 1 with a subscription
export class ExtendedComponent extends BaseComponent implements OnInit {
constructor(private update: UpdateService) {
super();
}
ngOnInit() {
this.update.subject.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
this.update.value ;
});
}
}
<p>Extended component exists!</p>
Neat, closing the component stops the ticker, so the subscription has been unsubscribed.
Bonus question: BaseComponent
does not act like a singleton, when you create an instance of an object it does not create shared instances of parent classes. Extending a class just adds properties and methods to that instance.
I'm not sure if I would recommend this, if someone overrides ngOnDestroy()
they need to call super.ngOnDestroy()
, which may be easy to forget. It's only four lines of code, probably better to explicitly put it in every component that needs it. Manual subscriptions should be pretty rare anyway, if you're using the async
pipe.
CodePudding user response:
I solved this in a project doing the following:
In base.component
:
private sub: any = {};
ngOnDestroy() {
Object.keys(this.sub).map(item => {
this.sub[item].unsubscribe();
})
}
Then in any component that extends:
this.sub.myService = this.myService.doStuff$.subscribe(......
With this method, the subscription stay active until the component is destroyed.