How to handle conditional observables in RxJS

Viewed 58

In the template data$ is displayed using the async pipe.

The user can either see all the data, or type in a search box and display the data filtered by the searchKey.

Two cases for data$:

  • no searchKey => data$ = getAllData(): Observable<someData[]>;
  • user enters searchKey => data$ = getDataBySearchKey(searchKey): Observable<someData[]>;

Pseudo code:

data$: Observable<someData[]>();
searchKeyChanged: Subject<string>();

ngOnInit() {
   this.data$ = this.dataService.getAllData();
   
   this.data$ = this.searchKeyChanged.pipe(switchMap(searchKey => this.dataService.getDataBySearchKey(searchKey)))
}

How do I handle this properly in RxJS?

1 Answers

You're most of the way there with your pseudocode. All you need is an initial value for the search key (I used null, with this implementation an empty string would work too). Then you can choose what to switch into.

data$: Observable<someData[]>();
searchKeyChanged: Subject<string>();

ngOnInit() {
  this.data$ = searchKeyChanged.pipe(
    startWith(null),
    switchMap(searchKey => 
      searchKey != null && searchKey?.length > 0 ?
      this.dataService.getDataBySearchKey(searchKey) :
      this.dataService.getAllData()
    )
  );
}

This is also the ideal use case for a switchMap as in-flight service calls will get canceled partway through if the user keeps typing and new searchKeys come in.

Aside: debounce is nice

If the user can change the searchKeys in rapid succession, I would recommend a debounce to smooth out the user experience.

ngOnInit() {
  this.data$ = searchKeyChanged.pipe(
    debounceTime(500),
    startWith(null),
    switchMap(searchKey => 
      searchKey != null && searchKey?.length > 0 ?
      this.dataService.getDataBySearchKey(searchKey) :
      this.dataService.getAllData()
    )
  );
}

This will now make a new service call if the user stops searchKeyChanged firing for half a second. It means that data$ won't stutter so much. It's also the usual rhythm for a user to stop a moment and then expect results.

If your service calls are slow to complete but can be canceled party-way (They're not converted promises, for example), then I would debounce AFTER the service call. This is more computation heavy but the results should appear 500ms faster than if you debounce before.

ngOnInit() {
  this.data$ = searchKeyChanged.pipe(
    startWith(null),
    switchMap(searchKey => 
      searchKey != null && searchKey?.length > 0 ?
      this.dataService.getDataBySearchKey(searchKey) :
      this.dataService.getAllData()
    ),
    debounceTime(500)
  );
}

Of course, you can change the time or use one of RxJS's other debounce operators

Related