I am running into a very strange situation with Angular Components and rxjs Subscriptions.
I have the following ngOnInit
and ngOnDestroy
functions
ngOnInit() {
zip(this.service.getMessage, this.service.getType)
.pipe(takeUntil(this.componentDestroyed$))
.subscribe(data => {
const notification = new Notification(data[0], data[1]);
this.notifications.push(notification);
});
}
ngOnDestroy(): void {
this.componentDestroyed$.next(true);
this.componentDestroyed$.complete();
}
The subscription is active after setting the values with a Source and Subject paradigm in a service file that looks like this:
private messageSource = new BehaviorSubject<string>('');
private message = this.messageSource.asObservable();
private typeSource = new BehaviorSubject<number>(-1);
private type = this.typeSource.asObservable();
...
...
set setMessage(message: string) {
this.messageSource.next(message);
}
set setType(type: number) {
this.typeSource.next(type);
}
The initial subscription works fine, as expected. However, navigating away from the Component and navigating back to the same Component runs the zip
subscription again in ngOnInit
, even after the component was destroyed when navigating away. How to prevent this from happening? I have also tried defining a subscription variable and calling unsubscribe
. I am stumped.
To expand upon my comment, RxJS BehaviorSubject
s hold the current value pushed to it and emit it immediately upon subscription. It also allows you to use the unique getValue()
function (or value
getter) on BehaviorSubject
that allows you to synchronously retrieve the current value held by it. Although usage of this getter is usually frowned upon.
So in your case, when you're routing back to the component, the subscription emits the previous held value by both the observables. Instead you could use RxJS Subject
that do not hold values and emit only after a value is pushed to it.
private messageSource = new Subject<string>();
private message = this.messageSource.asObservable();
private typeSource = new Subject<number>();
private type = this.typeSource.asObservable();
...
I'd also recommend you to see RxJS ReplaySubject
. It's a more flexible multicast observable. It takes in the number of notifications to be buffered and emits them immediately upon new subscriptions.
So a ReplaySubject(1)
(buffer 1) is similar to BehaviorSubject
except it doesn't need a default value. So if you were to subscribe to it when nothing has been pushed into it yet, it won't emit.
It doesn't help you with the current issue, but might be helpful where you wish to have the behavior of BehaviorSubject
but don't wish to deal with an unnecessary default value.