Search code examples
angularrxjscombinelatest

Rxjs scan does not trigger combineLatest


I am battling with combineLatest and scan rxjs operators (with Angular 8). Basically, I am displaying a list that can be filtered.

So I have a first Observable list$ = this.api.getAll()
and a second one filteredList$ = combineLatest(this.list$, this.form.valueChanges())
and, in my HTML, I'm displaying filteredList$ content using filteredList$ | async. This works fine (meaning, any change in the form update my filteredList$)

However, the end user is able to update one element of the list ; when he does, I update my list$ Observable this way :

this.api.update(listElement)
    .subscribe((updatedElmt) => {
         this.list$ = this.list$.pipe(
            concatMap(value => of(...value)),
            scan((acc, listElmt) => listElmt._id === updatedElmt._id ? [...acc, updatedElmt] : [...acc, listElmt], []),
        );
    });

This also works well.
However, the combineLatest is not triggered and therefore, my UI is never updated until I refresh my page. I assume it is because the list$ pointer has not change, so theere's no reason for the combineLatest to be triggered ; but how can I force it ?

BTW, using changeDetector to detectChanges or markForCheck does not work.


Solution

  • What exactly is it you want to achieve? If it is changing the updated Element in your list then you should consider something like this:

    1. Define your List as Subject:

       this.list$ = new Subject<ListType[]>();
      
    2. Get the current list and next the list$

       this.api.getAll().subscribe(this.list$.next); // dont forget to unsubscribe or youll get a memory leak
      
    3. Observe for updates:

      updateList$ = this.api.update(listElement).pipe(
          withLatestFrom(this.currentList$),
          map(([updatedElement, currentList]) => {
              return currentList.map(listElement => listElement._id === udaptedElement._id ? updatedElement : listElement),
          })
      ).subscribe(this.list$.next) // dont forget to unsubscribe (takeUntil()) should work here
      
    4. Combine your list$ with valueChanges

      combineLatest([this.list$, this.form.valueChanges()])).pipe(....)
      

    this.list$ = ... sets a new value to the property, but combineLatest ist subscribing to the old value of this.list$. Which will result in a memory leak.

    this.list$ = someObservableFunc$(); // gives first Observable<any>
    const observeList$ = combineLatest([this.list$, otherObservable$]); // gives combination of first Observable<any> and otherObservable$
    this.list$ = anotherObservableFunc$(); // gives second Observable<any> but observeList$ keeps subscription to first Observable<any>