Search code examples
angularconditional-statementsobservable

Angular 2+ > Conditional Observable Mapping


I try to do a conditional mapping of several Observables. First I want to do a request to see if I need to get IP. If true, I get Ip before send Post Request.

Here is my 2 Observables:

// NEED IP
this.init$ = this.http.get(API_URL + '/init').pipe(
    tap((response: any) => {
      this.init_need_ip = response.need_ip
    })
);

// IP WAN
this.ip_wan$ = this.http.get("https://api.ipify.org/?format=json").pipe(
    tap((response: any) => { this.ip_wan = response.ip })
);

And my fonction:

getIp(model, url, params, options = {}): Observable<any> {
    return this.init$.pipe(
    concatMap(() => {
      if (this.init_need_ip) {
        this.ip_wan$;
      }
    }),
    concatMap(() => {
      return this.http.post<{model}>(url, JSON.stringify(Object.assign({}, params, {ip_wan: this.ip_wan, ip_lan: JSON.stringify(this.ip_lan)})), options);
    }),
);

}

But I got an error:

Argument of type '() => void' is not assignable to parameter of type '(value: any, index: number) => ObservableInput'. Type 'void' is not assignable to type 'ObservableInput'.

If I try to return something in my else like:

return this.init$.pipe(
    concatMap(() => {
      if (this.init_need_ip) {
        this.ip_wan$;
      } else {
        return of(null);
      }
    }),
    concatMap(() => {
      return this.http.post<{model}>(url, JSON.stringify(Object.assign({}, params, {ip_wan: this.ip_wan, ip_lan: JSON.stringify(this.ip_lan)})), options);
    }),
);

I got another error from my service where the function is called:

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.


Solution

  • I would recommend passing data through the pipe, instead of assigning it to the component's properties, if they are not needed elsewhere.

    If I understand your code correctly, I think you should be able to do something like this:

    
    const init$ = this.http.get('/init');
    const ip$ = this.http.get("https://api.ipify.org/?format=json");
    
    init$.pipe(
      switchMap(response => {
        if(response.need_ip){
          return ip$;
        }else{
          return of({})
        }
      }),
      switchMap(response => {
        return this.http.post<{model}>(
          url, 
          JSON.stringify({
            ...params, 
            ip_wan: response.ip_wan,
            ip_lan: JSON.stringify(this.ip_lan),
          }),
          options
        );
      })
    )
    
    

    Ideally tho, you should have a service with this API calls, and use it as follows:

    this.ipService.init().pipe(
      switchMap(response => response.need_ip ? this.ipService.getIp() : of({})),
      switchMap(response => this.ipService.register(response.ip_wan, this.ip_lan))
    )