Reputation: 482
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
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);
},
() => { }
);
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
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