简体   繁体   中英

RxJS : Filter one observable using another observable

I am having one stream which carries the data from the API response and has another stream which emits the values which needs to be filtered from the source stream.

@Injectable({
    providedIn: 'root'
})
export class SmpService {
    private _smp$ = new ReplaySubject(1);
    private _deleteSubject = new BehaviorSubject(null);

    constructor(private http: HttpClient) {
        const allSmp$ = this.loadSmp().pipe(map(list => list.map(item => item.id)));
        const delete$ = this._deleteSubject.pipe(map(value => this.filterSmp(value)));

        allSmp$
            .pipe(
                combineLatest(delete$, (notifications, xf) => {
                    return xf(notifications);
                }),
                tap(x => console.log('console ', x))
            )
            .subscribe(this._smp$);
    }

    loadSmp(): Observable<any> {
        const contextPath = 'some_url';
        const url = this.uriPrefix + contextPath;
        return this.http.get(url).pipe(map((response: any) => response.notifications || []));
    }

    filterSmp(value) {
        return notifications => notifications.filter(notification => value !== notification);
    }

    deleteSmp(subscribeItem) {
        this._deleteSubject.next(subscribeItem);
    }

    getSmp(): Observable<any> {
        return this._smp$.asObservable();
    }
}

Filtering is working fine. But on page load, I am not able to get the initial API response rendered on the page. I only receive that when I trigger the deleteStream via some action.

Is there any way I can get the initial data even though deleteStream is not triggered?

Instead of Subject use BehaviorSubject with some default value. For example null .

private _deleteSubject = new BehaviorSubject(null);

BehaviorSubject will emit default value every time it's subscribed.

The main problem that you're running into is probably that $delete is a "cold" observable until _deleteSubject has a value. If you want _smp$ to be populated before _deleteSubject is given any values then you will need to initialize it with a default value, as mentioned by @m1ch4ls.

@Injectable({
    providedIn: 'root'
})
export class SmpService {

    private _smp$ = new ReplaySubject(1);
    private _deleteSubject = new BehaviorSubject(null);

    constructor(private http: HttpClient) {
        const allSmp$ = this.loadSmp().pipe(map(list => list.map(item => item.id)));
        const delete$ = this._deleteSubject.pipe(map(value => value ? this.filterSmp(value) : (notifications => notifications)));

        delete$
            .pipe(
                withLatestFrom(allSmp$, (xf, notifications) => {
                    return xf(notifications);
                }),
                tap(x => console.log('console ', x))
            )
            .subscribe(this._smp$);
    }

    loadSmp(): Observable<any> {
        const contextPath = 'some_url';
        const url = this.uriPrefix + contextPath;
        return this.http.get(url).pipe(map((response: any) => response.notifications || []));
    }

    filterSmp(value) {
        return (notifications) => notifications.filter(notification => value !== notification);
    }

    deleteSmp(subscribeItem) {
        this._deleteSubject.next(subscribeItem);
    }
}

Another approach of filtering one Observable using another can be

    private _smp$ = new ReplaySubject(1);
    private _deleteSubject = new Subject();

    constructor(private http: HttpClient) {
        const allSmp$ = this.loadSmp().pipe(map(list => list.map(item => item.id)));
        const delete$ = this._deleteSubject.pipe(
            map(value => (value ? this.filterSmp(value) : notifications => notifications))
        );

        allSmp$.merge(delete$)
            .pipe(
                startWith([]),
                scan((acc, value) => {
                    if (typeof value === 'function') {
                        return [...value(acc)];
                    }
                    return [...acc, ...value];
                })
            )
            .subscribe(this._smp$);
    }    

In this, we have done following :

  1. replaced BehaviourSubject(null) -> Subject()
  2. replaced combineLatest -> merge
  3. added startWith and scan to keep the persistent internal state

And voila, it works. But still looking for other better solutions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM