0

I have a simple component which selects something from the state and listenes for the change:

private items$: Observable<Item[]>;
private alive: boolean = true;

contructor(private store: Store<state>) {
  this.items$ = this.store.select(selectItems);
}

ngOnInit() {
  this.items$
    .pipe(
      takeWhile(() => this.alive)
    )
    .subscribe((items: Item[]) => {
      console.log('items changed!', items);
      // dispatch some actions
    });
}

ngOnDestroy() {
  this.alive = false;
}

It works perfectly - each time the items change, I see the "items changed!" string logged. When I redirect somewhere else and the component is destroyed, the subscription doesn't run.

But when I redirect back to the component and subscription is active again, it immediately executes the same amount of changes as at the beginning, even though nothing changed.

For example, when I enter the page:

items changed! null
items changed! ['a', 'b', 'c']
items changed! ['a', 'b', 'c', 'd']

When I redirect and go back to the component:

items changed! ['a', 'b', 'c', 'd']
items changed! ['a', 'b', 'c', 'd']
items changed! ['a', 'b', 'c', 'd']

What's wrong here? I've tried takeUntil and a subject, I tried unsubscribing manually - nothing solves the problem.

khernik
  • 2,059
  • 2
  • 26
  • 51

3 Answers3

1

Use takeUntil with a subject

private items$: Observable<Item[]>;
private finalised = new Subject<void>();

contructor(private store: Store<state>) {
  this.items$ = this.store.select(selectItems);
}

ngOnInit() {
  this.items$
    .pipe(
      takeUntil(this.finalised)
    )
    .subscribe((items: Item[]) => {
      console.log('items changed!', items);
      // dispatch some actions
    });
}

ngOnDestroy() {
  this.finalised.next();
  this.finalised.complete();
}
Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
0

Unsubscribe, or takeUntil will be done after component destruction and items are changes before destruction.

You get items changed! message 3 times because in items$ there are 3 next events, so subscriber was called 3 times.

If you want to ignore change events with objects the same as in previous event I suggest to use distinctUntilChanged operator.

I will looks like this:

items$
  .pipe(distinctUntilChanged((previous, current) => previous[0] === current[0] && ...)) /*
  .subscribe(items => console.log('items changed!', items));
  • distinctUntilChanged has optional callback, but in you case you will need it because default one is (previous, current) => previous === current but in case of array of string ['a', 'b', 'c', 'd'] !== ['a', 'b', 'c', 'd'] so you have to write comparing function.

docs

Przemyslaw Jan Beigert
  • 2,351
  • 3
  • 14
  • 19
0

I think your problem is in fact that you're not unsubscribing when a component is destroyed.

You're using takeWhile but this operator only reacts when its source emits. This means that setting this.alive = false; does nothing. It could complete the chain only if this.items$ emitted after that. It would be caught by takeWhile and that would complete the chain. But this probably doesn't happen.

The recommended way is using takeUntil or manually unsubscribing. This has been discussed here many times, this answer should tell you what to do exactly: Angular/RxJs When should I unsubscribe from `Subscription`.

martin
  • 93,354
  • 25
  • 191
  • 226