Search code examples
angularrxjsobservable

rxjs using combineLatest to filter data stream - stream data is empty when it should not be


I am using combine latest in an angular component to bring together a stream of data with a filter text input:

export class RecordSearchComponent implements OnDestroy {
  @Input() gridData: Observable<TrainingRecord[]> = of([]);

  private filterSubject = new BehaviorSubject<string>('');
  private readonly debounceTimeMs = 300;

  private filterInput$ = this.filterSubject.pipe(
    debounceTime(this.debounceTimeMs),
    distinctUntilChanged(),
  )

  filteredData$ = combineLatest([this.filterInput$, this.gridData]).pipe(
    tap(([filter, data]) => console.log(filter, data)),
    switchMap(([filter, data]) => {
      if (filter === '' || filter === null) {
        return of(data);
      }
      const filterDescriptor = {
        field: 'nameFull',
        operator: 'contains',
        value: filter,
        ignoreCase: true,
      };
      return of(filterBy(data, filterDescriptor));
    })
  );

  ngOnDestroy(): void {
    this.filterSubject.complete();
  }

  onFilter() {
    this.filterSubject.next(this.inputText);
  }
}

The filterSubject is working and I can see the results in the console.

The problem is that the "gridData" value inside combineLatest is always empty. But I can confirm that data is coming in if I subscribe to the gridData on its own in the template. (gridData | async) returns all the records.


Solution

  • I think if we initialize like this using @Input you might not get the value on initialization, as a safety, you can initialize on ngOnInit.

    Also please note: The gridData should have a value when the component is initializing only then it will work!

    export class RecordSearchComponent implements OnDestroy {
      @Input() gridData: Observable<TrainingRecord[]> = of([]);
    
      private filterSubject = new BehaviorSubject<string>('');
      private readonly debounceTimeMs = 300;
      filteredData$!: Observable<TrainingRecord[]> = of([]);
    
      private filterInput$ = this.filterSubject.pipe(
        debounceTime(this.debounceTimeMs),
        distinctUntilChanged(),
      )
    
      ngOnInit() {
        this.filteredData$ = combineLatest([this.filterInput$, this.gridData]).pipe(
            tap(([filter, data]) => console.log(filter, data)),
            switchMap(([filter, data]) => {
              if (filter === '' || filter === null) {
                return of(data);
              }
              const filterDescriptor = {
                field: 'nameFull',
                operator: 'contains',
                value: filter,
                ignoreCase: true,
              };
              return of(filterBy(data, filterDescriptor));
            })
          );
      }
    
      ngOnDestroy(): void {
        this.filterSubject.complete();
      }
    
      onFilter() {
        this.filterSubject.next(this.inputText);
      }
    }