I have an auth service with a login method and a login component.
From my login components' onSubmit method I call the services' login method and subscribe to the answer.
The challenging part is, that in that login method I have to do multiple succeeding requests (one call to get the generated token for a user name, then use that token to encrypt an entered password and make another call to then really login on the server). Not sure how to do this, tried many approaches, but I'm still quite new to angular and wonky when it comes to Observable.
login component:
this.authenticationService.login(this.f.username.value, this.f.password.value)
.pipe(first())
.subscribe(
data => {
this.router.navigate([this.returnUrl]);
},
error => {
this.alertService.error(error);
this.loading = false;
});
auth service:
login(username, password) {
return this.http.post<any>(`${config.apiUrl}/users/authenticate`, { username, password })
.pipe(map(user => {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
return user;
}));
}
I tried multiple nested calls using http.get().pipe and lots of variants of that, but obviously I'm still missing some basic understanding.
The snippet/idea is from here: https://jasonwatmore.com/post/2019/06/10/angular-8-user-registration-and-login-example-tutorial
Edit:
I got a solution, but it's ugly, some improvements possible ?
return new Observable((observer) => {
this.http.get<any>(urlToken)
.pipe(map(answer => {
if (answer['type'] != 'Success') { return; }
let token = answer['message'];
urlLogin += '&passphrase=' + someSalt(username, password, token);
return this.http.get<any>(urlLogin)
.pipe(map(answer2 => {
if (answer2['type'] != 'Success') { return; }
let sessionId = answer2['message'];
let user = new User();
user.username = username;
user.sessionId = sessionId;
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
observer.next(user);
})).subscribe();
})).subscribe();
});
when chaining observables in a sequential manner a concept called higher-order-mapping is used (i suggest this article). one of these higher-order-mapping operators is concatMap and combining it with tap operator will help you achive your goals in the correct way. your solution is not just ugly :( it is not testable and unnecessarily complicated. your login
method should be like this;
login(username, password) {
/** we use concatmap here because return value of this callback is going to be another observable.
* we are mapping result of an observable to another obervable. higher-order-mapping ;)
*/
return this.http.get<any>(urlToken).pipe(concatMap(answer => {
/** notice that we have to return an observable in concatMap callback, that's why we use `of()` */
if (answer['type'] != 'Success') { return of(false); }
let token = answer['message'];
urlLogin += '&passphrase=' + someSalt(username, password, token);
/** we use tap here baceuse we are not modifying the response in anyway. we just want to do some
* side operations without affecting return value of this obervable.
*/
return this.http.get<any>(urlLogin).pipe(tap(answer2 => {
/** we just return here beause tap operator is just doing side operations */
if (answer2['type'] != 'Success') { return; }
let sessionId = answer2['message'];
let user = new User();
user.username = username;
user.sessionId = sessionId;
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
observer.next(user);
}));
}));
}
here is a demonstration of above concepts https://stackblitz.com/edit/angular-qvhay8