1
votes

In a parent component I have a stream of Tour[] tours_filtered: Observable<Tour[]> which I assign in the subscribe function of an http request

this.api.getTours().subscribe(
  result => {
    this.tours_filtered = of(result.tours);
  }
)

in the view I display the stream using the async pipe

<app-tour-box [tour]="tour" *ngFor="let tour of tours_filtered | async"></app-tour-box>

Up to here all works as expected. In a child component I have an input text which emits the value inserted by the user to filtering the array of Tour by title.

In the parent component I listen for the emitted values in a function, I switch to new stream of Tour[] filtered by that value using switchMap

onSearchTitle(term: string) {
  this.tours_filtered.pipe(
    switchMap( 
      (tours) => of( tours.filter((tour) => tour.name.toLowerCase().includes(term)) )
    )
  )
}

I thought that the async pipe was constantly listening to reflect the changes to the array to which it was applied and so I thought I didn't have to subscribe in the function above, but nothing change in the view when I type in the input to filtering the results.

The results are updating correctly if I assign the new stream to the original array in the subscribe function


    onSearchTitle(term: string) {
        this.tours_filtered.pipe(
          switchMap((tours) => of(tours.filter((tour) => tour.name.toLowerCase().includes(term))))
        ).subscribe( val => { this.tours_filtered = of(val); })
      }

Is this procedure correct? Could I avoid to subscribe because I already use the async pipe? There is a better way to reach my goal?

EDITED:

Maybe I found a solution, I have to reassing a new stream to the variable just like this

onSearchTitle(term: string) {
    this.tours_filtered = of(this.city.tours).pipe(
      switchMap((tours) => of(tours.filter((tour) => tour.name.toLowerCase().includes(term))))
    );
  }

and I don't need to subscribe again, the results in the view change according to the search term typed by the user. Is this the correct way?

1

1 Answers

1
votes

I think in your situation the solution should work as follows:

onSearchTitle(term: string) {
  this._searchTerm = term;
  this.tours_filtered = of( 
    this.city.tours.filter((tour) => tour.name.toLowerCase().includes(term))
  )
}

Because in your example you don't change the observable which is used in ngFor. Thus it's not working.

However, I don't see the reason of using observables here unless this is the first step and you're going to fetch this data from server in future


UPDATE

The best solution for you would be to consider your input as an observable and watch for the changes:

// your.component.ts
export class AppComponent  {

  searchTerm$ = new BehaviorSubject<string>('');
  results = this.search(this.searchTerm$);

  search(terms: Observable<string>) {
    return terms
      .pipe(
        debounceTime(400),
        distinctUntilChanged(),
        switchMap(term => {
          return of(this.city.tours.filter((tour) => tour.name.toLowerCase().includes(term)))
        }
        )
      )
  }

}
// your.template.html
...
<input type="" (input)="searchTerm$.next($event.target.value)">
...

Additionally it would be great to add debounceTime and distinctUntilChanged for better user experience and less search requests.

See full example for the details. Also please, refer to this article for more detailed explanations