Reputation: 162
I'm still learning rxjs and run into a problem.
I have a service with a BehavioralSubject intended to hold a single value and emmiting it to other components when changed. The other components will change the value so they comunciate bettween components- I am using it with a component that does an http request to save a document when it recieves a specific value from the subscription (another component is in charge of changing that value). Once I start the application it works fine, but the second time it emits the value 2 times, sending 2 http requests, 3 the 3rd time, 4 the 4th, and so on...
Here is the code for the service
save.service.ts
export class SaveService {
readonly state: BehaviorSubject<SAVE_STATE>;
constructor() {
this.state = new BehaviorSubject(SAVE_STATE.IDLE);
}
public save() {
this.state.next(SAVE_STATE.SAVE);
}
public reset() {
this.state.next(SAVE_STATE.RESET);
}
public changed() {
this.state.next(SAVE_STATE.CHANGED);
}
public idle() {
this.state.next(SAVE_STATE.IDLE);
}
public loading() {
this.state.next(SAVE_STATE.LOADING);
}
}
Here is the component that changes the value
save-options.component.ts
private show: boolean;
private loading: boolean;
constructor(private saveService: SaveService) { }
ngOnInit() {
this.show = false;
this.saveService.state.subscribe((state) => {
this.show = state === SAVE_STATE.IDLE ? false : true;
this.loading = state === SAVE_STATE.LOADING ? true : false;
});
}
saveAction() {
this.saveService.save();
}
discardAction() {
this.saveService.reset();
}
Here is the function in the component that recives the value and makes the request, this method is called in the ngOnInit()
create-or-edit.component.ts
private listenToSaveEvents() {
this.saveService.state.subscribe((state) => {
console.log(state);
switch(state){
case SAVE_STATE.SAVE:
this.saveStore();
break;
case SAVE_STATE.RESET:
this.undo();
break;
default:
break;
}
});
}
The later function is the one executing multiple times incrementally. the result of the log is: First execution
0
3
4
3
Second execution
0
3
4
3
0
3
(2)4
(2)3
I might be using BehaviorSubject wrong but can't manage to figure out why, thank you.
Upvotes: 1
Views: 178
Reputation: 31105
Probably the create-or-edit.component.ts
component is created and destroyed multiple times. As a general rule, it is always safe to unsubscribe in the ngonDestroy()
hook to avoid memory leaks.
Option 1
You could try to unsubscribe the subscription in the ngDestroy()
hook. Try the following
create-or-edit.component.ts
stateSubscription: any;
private listenToSaveEvents() {
this.stateSubscription = this.saveService.state.subscribe((state) => {
console.log(state);
switch(state) {
case SAVE_STATE.SAVE:
this.saveStore();
break;
case SAVE_STATE.RESET:
this.undo();
break;
default:
break;
}
});
}
ngOnDestroy() {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
}
Option 2
It might get difficult to keep track of all the subscriptions and unsubscribe in the ngOnDestroy
hook. You could use this solution to workaround it.
Setup the following shared function:
// From https://stackoverflow.com/a/45709120/6513921
// Based on https://www.npmjs.com/package/ng2-rx-componentdestroyed
import { OnDestroy } from '@angular/core';
import { ReplaySubject } from 'rxjs';
export function componentDestroyed(component: OnDestroy) {
const oldNgOnDestroy = component.ngOnDestroy;
const destroyed$ = new ReplaySubject<void>(1);
component.ngOnDestroy = () => {
oldNgOnDestroy.apply(component);
destroyed$.next(undefined);
destroyed$.complete();
};
return destroyed$.asObservable();
}
Now all there is to do is to implement ngOnDestroy
in the component and add takeUntil(componentDestroyed(this))
to the pipe.
import { pipe } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
private listenToSaveEvents() {
this.stateSubscription = this.saveService.state
.pipe(takeUntil(componentDestroyed(this))) // <-- pipe it in here
.subscribe((state) => {
console.log(state);
switch(state) {
case SAVE_STATE.SAVE:
this.saveStore();
break;
case SAVE_STATE.RESET:
this.undo();
break;
default:
break;
}
});
}
ngOnDestroy() {
}
Upvotes: 2