Davy
Davy

Reputation: 6441

Dont understand why my withLatestFrom operator is being triggered

Im slowly starting to grasp rxjs, but once in a while i encounter things that puzzle me.

In this case its about the withLatestFrom operator. The rxjs statement below sits in the OnInit method of my angular component. When the screen loads, i see that the api call in the getApiData() method is executed twice, while im sure that the userEventSubject$ was never triggered (thats why i have the first tap operator).

What i expect to happen is that the getApiData() method only gets called when userEventSubject$.next() is called, and never when the screen loads ...

this.userEventSubject$
    .pipe(
        tap(creditNote => console.log('tapped!')),
        takeUntil(this.ngUnsubscribe$),
        filter(userEventData => this.checkValidity(userEventData)),
        withLatestFrom(this.apiService.getApiData()),
        mergeMap(([userEventData, apiData]) => {
            let modalRef = this.modalService.show(ModalDialogComponent, {
                initialState: { apiData }
            });
            let instance = <ModalDialogComponent>modalRef.content;

            return instance.save.pipe(
                mergeMap((info) =>
                    this.apiService.saveSomeData(userEventData, info).pipe(
                        catchError((error, caught) => {
                            instance.error = error;
                            return empty();
                        })
                    )
                ),
                tap(response => modalRef.hide())
            );
        })
    )
    .subscribe((response) => {
        this.handleResponse(response);
    });

Fixed version:

this.userEventSubject$
    .pipe(
        tap(creditNote => console.log('tapped!')),
        takeUntil(this.ngUnsubscribe$),
        filter(userEventData => this.checkValidity(userEventData)),
        mergeMap(userEventData =>
            this.apiService.getApiData().pipe(
                map(data => {
                    return { userEventData, data };
                })
            )
        ),
        mergeMap(values => {
            let modalRef = this.modalService.show(ModalDialogComponent, {
                initialState: { data: values.apiData }
            });
            let instance = <ModalDialogComponent>modalRef.content;

            return instance.save.pipe(
                mergeMap((info) =>
                    this.apiService.saveSomeData(userEventData, info).pipe(
                        catchError((error, caught) => {
                            instance.error = error;
                            return empty();
                        })
                    )
                ),
                tap(response => modalRef.hide())
            );
        })
    )
    .subscribe((response) => {
        this.handleResponse(response);
    });

Upvotes: 3

Views: 1126

Answers (1)

kvetis
kvetis

Reputation: 7331

When you're building the pipe, the this.apiService.getApiData() is not in an arrow function, but it's executed right away. It doesn't matter that the call passed as an argument. The expression gets executed as any other JS call would (try putting console.log in the same place).

You could do .concatMap(userData => this.apiService.getApiData().map(apidata => {userData, apiData})) (or switchMap), but that would call the API always. I don't know what bests suits your needs.

Upvotes: 5

Related Questions