Home > Software engineering >  Why does CombineLatest modify observable
Why does CombineLatest modify observable

Time:12-22

In my Angular app I have this simple stream for a search bar.

.ts

user$: Observable<User> = this.authService.user$;
ally$: Observable<UserAndMatch> = this.user$.pipe(
  switchMap((user: User) =>
    this.userService.getFollowing(user)
  shareReplay(1),
);
filteredAlly$: Observable<UserAndMatch> = new Observable<UserAndMatch>();
@ViewChild('search') search: ElementRef;

ngAfterViewInit() {
  const query$ = fromEvent(this.search.nativeElement, 'input').pipe(
    debounceTime(200),
    map(() => this.search.nativeElement.value),
    startWith(''),
    map((query: string) => query.trim().toLowerCase()),
    distinctUntilChanged(),
  );

  this.filteredAlly$ = combineLatest([query$, this.ally$]).pipe(
    map(([query, ally]: [string, UserAndMatch]) => {
      return !query ? ally : this.allyFilter(ally, query)
    }),
  );
}

 allyFilter(ally, query) {
   ally.users = ally.users.filter(user => user.name.includes(query));
   return ally;
 }

.html

  <form autocomplete="off">       
       <input
         #search
       />
   </form>
   <ng-container *ngrxLet="filteredAlly$ as ally"></ng-container

The stream works as expected when typing a word, but when the user deletes some letters the array from filteredAlly$ doesn't repopulate and, logging the values in the ally$ pipeline, I found out that ally$ values are being filtered too, while I wanted them to be a "backup" to apply some filter function to which should return filteredAlly$

CodePudding user response:

The probable culprit is the first line of allyFilter:

ally.users = ally.users.filter(user => user.name.includes(query));

You're modifying ally.users in place, instead of creating a copy. This means that you're shortening the list of allies when the user is typing, and you're not getting a "fresh" copy of allies later, when the user deletes the characters.

It will probably suffice to shallow-copy ally object in allyFilter, like so:

 allyFilter(ally, query) {
   return {
     ...ally,
     users: ally.users.filter(user => user.name.includes(query));
   };
 }
  • Related