How can I combine the latest result from my HttpClient and FormControl.valueChanges to apply a filter?

86 Views Asked by At

I have a component where I am using a service to fetch data that also has a search filter

I would like the Observable to be the end result of the HttpClient with the filter applied from the FormControl searchControl so that reportPeriods$ is a stream of the filtered result

This works but I am noticing that my map function is firing N times for each item that exists in ReportPeriod[] and I am not sure this is the correct usage

The request to fetch getReportPeriods(12) should only fire once

export class ReportPeriodComponent {
    reportPeriods$: Observable<ReportPeriod[]>;
    searchControl = new FormControl<string>('');
    private destroyRef = inject(DestroyRef);

    constructor(public qaService: QaService, private reportPeriodService: ReportPeriodService) {
        this.reportPeriods$ = combineLatest({
            reportPeriods: this.reportPeriodService.getReportPeriods(12),
            text: this.searchControl.valueChanges.pipe(startWith(''))
        }).pipe(
            takeUntilDestroyed(this.destroyRef),
            map(({ reportPeriods, text }) => {
                if (text && text.length > 0) {
                    return reportPeriods.filter((reportPeriod) => reportPeriod.friendlyName.toLowerCase().indexOf(text.toLowerCase()) !== -1);
                } else {
                    return reportPeriods;
                }
            })
        );
    }
}

ReportPeriodService

getReportPeriods(monthsAgo: number): Observable<ReportPeriod[]> {
    let params = new HttpParams().set('monthsAgo', monthsAgo);

    return this.http.get<ReportPeriod[]>(`${this.url}/report-periods`, { params }).pipe(
        map((reportPeriods) => {
            return reportPeriods
                .map((reportPeriod) => {
                    return {
                        ...reportPeriod,
                        startDate: new Date(reportPeriod.startDate),
                        endDate: new Date(reportPeriod.endDate)
                    };
                })
                .sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
        })
    );
}

enter image description here

enter image description here

1

There are 1 best solutions below

0
Tyler V On

switchMap is the perfect operator to solve something like this, I will also say the reason for the observable appearing to fire duplicate times if because I was also referencing that observable with an async pipe in the *ngFor of my template

    this.reportPeriods$ = this.reportPeriodService
        .getReportPeriods(12)
        .pipe(tap((reportPeriods) => this.qaService.selectedReportPeriod.set(reportPeriods[0])))
        .pipe(
            takeUntilDestroyed(this.destroyRef),
            switchMap((reportPeriods) => {
                return this.searchControl.valueChanges.pipe(
                    startWith(''),
                    map((text: string | null) => {
                        if (text && text.length > 0) {
                            return reportPeriods.filter((reportPeriod) => reportPeriod.friendlyName.toLowerCase().indexOf(text.toLowerCase()) !== -1);
                        } else {
                            return reportPeriods;
                        }
                    })
                );
            })
        );