How to make nesting subscription in Angular?

Viewed 443

Hello I'm having a nesting subscription in my angular and i'm handling in the below way and heard it's a bad approach.

Can anyone let me know the best possible way to do? It will really help me to learn.Below is my code, please have a look. TIA

login.ts

 login(email: string, password: string) {
    const user = new FormData();
    user.append("username", email);
    user.append("password", password);
    this.http
      .post<any>("api1", user)
      .subscribe((response) => {
        this.myToken = response.access_token;
        console.log(this.myToken);
        if (this.myToken) {
          const body = new HttpParams()
            .set("username", email)
            .set("password", password);

          return this.http
            .post<any>("api2", body, {
              headers: new HttpHeaders({
                "Content-Type": "application/x-www-form-urlencoded",
                Authorization: `${this.myToken}`,
              })
            })
            .subscribe((response) => console.log(response));
        } else {
          alert("error: Not authorized");
        }
      });
  }

2)When my component is loaded i need to check my get api response. If I get get API response == null I need to post data using the same form. When my get API response != null, then I need to patch the values to the form and should able to update it using PUT API

form.ts

 getVal() {
      http.get<any>(API).subscribe(response => {
       this.getResponse= response;
       if (this.getResponse != null) {
          this.form.patchValue({.     //append the values to the form
            username: response.username,
          })

      })
    }

   onRegisterSubmit(form) {
   this.username = form.value
   console.log(form.value);
    if (this.getResponse != null) {
       //I want to enable update button and update api here
          http.put<any>(api, this.username).subscribe(resposne => 
          console.log(response) )
        } if(response == null) {
           http.post<any>(api, this.username).subscribe(resposne => 
          console.log(response) )
        //I want to send data using post method.
        }
 }
2 Answers

Nested subscriptions are bad because there would multiple subscriptions that have no direct dependencies and would lead to more potential memory leaks. You need to use RxJS higher order operators (like switchMap) to combine multiple observables. Try the following

import { EMPTY } from 'rxjs';
import { switchMap } from 'rxjs/operators';

login(email: string, password: string) {
  const user = new FormData();
  user.append("username", email);
  user.append("password", password);
  this.http.post<any>("api1", user).pipe(
    switchMap((response) => {
      this.myToken = response.access_token;
      console.log(this.myToken);
      if (this.myToken) {
        const body = new HttpParams().set("username", email).set("password", password);
        const headers = new HttpHeaders({
          "Content-Type": "application/x-www-form-urlencoded",
          Authorization: `${this.myToken}`
        });
        return this.http.post<any>("api2", body, { headers: headers });
      } else {
        alert("error: Not authorized");
        return EMPTY;
      }
    })
  ).subscribe(
    (response) => { console.log(response) },
    (error) => { }
  );
}

Update - wrong

In the onRegisterSubmit() function, we check if the this.getResponse variable is defined already, but it is assigned a value asynchronously in the getVal() function.

onRegisterSubmit(form) {
  this.username = form.value;
  console.log(form.value);
  if (this.getResponse) {         // <-- implies `this.getResponse` is already assigned in `onRegisterSubmit()` function
    http.put<any>(api, this.username).pipe(
      switchMap(putResponse => {
        console.log(putResponse);
        if(!putResponse) {
          return http.post<any>(api, this.username);
        }
        return EMPTY;
      })
    ).subscribe(
      postResponse => { console.log(Postresponse) },
      error => { }
    );
  }
}

The switchMap() operator should return an observable. So if we have no observable to return, we could use RxJS EMPTY constant. It creates an observable that immediately emits the complete notification.

Update 2

To conditionally subscribe to an observable, you could use RxJS iif method.

onRegisterSubmit(form) {
  this.username = form.value;
  console.log(form.value);
  iif(() => 
    this.getResponse,
    http.put<any>(api, this.username),
    http.post<any>(api, this.username)
  ).subscribe(
    respone => { console.log(response) },
    error => { }
  );
}

In your last doubt. You need not to wrap around Put as this.getResponse is the value not observable so its better to use if and else condition than using any Rxjs Operator. As using if and else is not the nested subscription for onRegisterSubmit method.

or you can use it like

onRegisterSubmit(form) {
 this.username = form.value;
  iif(() => 
    this.getResponse,
    http.put<any>(api, this.username),
    http.post<any>(api, this.username)
  ).subscribe(
    respone => { console.log(response) },
    error => { }
  );
}
Related