2

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.
        }
 }
Teja
  • 131
  • 1
  • 12
  • @wentjun Yeah but i'm not clear with my second case `form.ts`. How can I map into `mergeMap` or `forkJoint` – Teja Jun 07 '20 at 07:29

2 Answers2

2

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 => { }
  );
}
Community
  • 1
  • 1
ruth
  • 29,535
  • 4
  • 30
  • 57
  • So instead of subscribing we use `switchmap` to stores the response of first req and will be assigned to next call. Finally the subscription is done in complete level ? – Teja Jun 07 '20 at 07:21
  • If apply `switchMap` for my `form.ts` I'm not able to perform operation. – Teja Jun 07 '20 at 07:27
  • And what's the use of `EMPTY` ? – Teja Jun 07 '20 at 07:44
  • Yes the subscription is done on the final observable returned from the `switchMap` operator. I've updated the answer. – ruth Jun 07 '20 at 07:49
  • last doubt. `getResponse` will have `value` or `null` . If it has value it should go to put. if it's null it should go to post. Here everything is wrapped in if(getResponse). How can it handle? There will be always put response, then when will it get in to post response? – Teja Jun 07 '20 at 08:16
  • In that case I misunderstood your question and made the POST request dependent on the PUT request. I've update the answer to conditionally select PUT or POST request based on the `this.getResponse` variable. – ruth Jun 07 '20 at 08:24
  • Updated my question .Please have a look – Teja Jun 07 '20 at 08:25
  • @sai: I believe the 2nd update should solve the issue. – ruth Jun 07 '20 at 08:27
  • How can it understand like to which method the call should go? Do I need to write if statement for checking the value is null or has some value? Or is there anything like `if ==true` call first method `if==false` call second method? – Teja Jun 07 '20 at 08:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215456/discussion-between-michael-d-and-sai). – ruth Jun 07 '20 at 08:30
0

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 => { }
  );
}
Mahaveer
  • 1
  • 2
  • Can you post the answer with explanation? – Teja Jun 07 '20 at 08:42
  • [Link](https://stackblitz.com/edit/rxjs-iif?file=index.ts&devtoolsheight=100) in the given example you can see that it works on `Subscribe to first or second observable based on a condition`. So in your case if `this.getResponse` will be not null than `put `call will be made and subscribed otherwise `Post` call will be made and subscribed. I believe that is your intent. – Mahaveer Jun 07 '20 at 08:52
  • Tnd the above solution is using RXJS only, right? And my in my case `get` should be init on ngOnInit and remaining two calls will be executed on submit button – Teja Jun 07 '20 at 09:03
  • I got the above solution from @ Michael D [link](https://stackoverflow.com/a/62241971/10928596) – Teja Jun 07 '20 at 09:05