Zesa Rex
Zesa Rex

Reputation: 482

How to make a HTTP Request within another subscribe()?

EDIT: So I just tried my code again and now it works as intended.. I don't know what changed. Anyway, I am still open to answers regarding the question "How to do it properly"

so I have been playing around with angular and typescript for some days now. (I am coming from .NET and Java).

It works well so far, but I encountered a problem.

See this example: When User logs SUCCESSFULLY in, I want to request additional user-information immediately to store/work with. Thats what I came up with.

this._authService.login(this.username, this.password).subscribe(
  (loginResult: LoginResult) => {
    if (loginResult) {
      console.log(loginResult)

      // NOW COLLECT MORE DATA FROM SERVICE 
      this.userService.getMoreUserData().subscribe(
        (data) => {
          console.log(data);
        },
        (err) => {
          console.error(err);
        },
        () => {console.log("Second Sub finished");}
      );

      this.shared.changeLoginStatus(true);
      this.shared.changeUser(decodedUser);

      console.log("Redirect to Profile Page");
      this.router.navigate(["/profile"]);

    } else { console.log("Error in responseObj"); }
  },
  (err) => {},
  ()=>{}
);

Sadly, this does not work. It always returns "undefined" when printing the "data". The Observable itself works fine when implemented in a standard-fashion-way in ngInit.

So can someone tell me, why doesn't it work and how to properly solve my problem?

I think, theoretically it should work.. but it does not. Maybe it is because I redirect to another Component quite immediately, thus leaving the subscription no time to execute? Nah.. That does not make sense because it prints the "data", but as undefined.

Any ideas?

Upvotes: 0

Views: 1130

Answers (2)

Barremian
Barremian

Reputation: 31125

Instead of nested subscriptions, you could try RxJS higher order mapping operators like switchMap. Try the following

this._authService.login(this.username, this.password).pipe(
  switchMap(loginResult: LoginResult) => {
    console.log(loginResult)
    if (loginResult) {
      return this.userService.getMoreUserData();
    } else {
      throwError("Error in responseObj");       // <-- throw error if `loginResult` isn't defined
    }
  }
).subscribe(
  (data) => {
    console.log(data);
    this.shared.changeLoginStatus(true);
    this.shared.changeUser(decodedUser);

    console.log("Redirect to Profile Page");
    this.router.navigate(["/profile"]);    // <-- redirect only if the inner observable emits
  },
  (err) => {
    console.error(err);
  },
  () => { }
);

Update - Explanation

switchMap operator maps to the source observable (here this._authService.login(this.username, this.password)) to an inner observable (here this.userService.getMoreUserData()) and returns the modified observable from the inner observable. So essentially it obtains the values from the source observable, modifies it and returns another modified observable.

Other higher order mapping operators would be mergeMap, concatMap and exhauseMap. Each has their own unique properties when dealing with a stream of notifications from the source observable. You could learn more about them here.

Upvotes: 1

Jo Carrasco
Jo Carrasco

Reputation: 324

Authentication is based on a single request for user validation. You don't actually need a stream of data to authenticate. If you want to keep your code structure you can follow Michael answer. Here's my solution using a Promise to get the first value of the Observable provided by the HttpClient.

const loginResult: LoginResult = await this._authService.login(this.username, this.password).toPromise();

if (loginResult === true)  {
  this.userService.getMoreUserData().subscribe((data) => { ///do stuff });
  this.shared.changeLoginStatus(true);
  this.shared.changeUser(decodedUser);
  console.log("Redirect to Profile Page");
  this.router.navigate(["/profile"]);
} else {
  console.error('Auth error');
}

Upvotes: 1

Related Questions