Angular 8 订阅完成多个成功的请求,如何?
Angular 8 subscribe on finish of multiple succeeding requests, how to?
我有一个带有登录方法和登录组件的身份验证服务。
从我的登录组件的 onSubmit 方法调用服务的登录方法并订阅答案。
具有挑战性的部分是,在该登录方法中,我必须执行多个成功的请求(一次调用获取用户名生成的令牌,然后使用该令牌加密输入的密码,然后再调用一次真正登录服务器)。不确定如何做到这一点,尝试了很多方法,但我对 angular 还是很陌生,而且在 Observable 方面也很不稳定。
登录组件:
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;
});
授权服务:
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;
}));
}
我尝试使用 http.get().pipe 及其许多变体进行多次嵌套调用,但显然我仍然缺乏一些基本的理解。
snippet/idea 来自这里:https://jasonwatmore.com/post/2019/06/10/angular-8-user-registration-and-login-example-tutorial
编辑:
我找到了解决方案,但它很丑陋,是否可以进行一些改进?
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();
});
当以顺序方式链接可观察对象时,使用了一个称为 higher-order-mapping 的概念(我建议 this article). one of these higher-order-mapping operators is concatMap and combining it with tap 运算符将帮助您以正确的方式实现您的目标。您的解决方案不仅丑陋:(它不可测试且不必要地复杂。您的 login
方法应该是这样的;
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);
}));
}));
}
我现在在进行后续调用时经常使用另一种模式。通常更干净。
封装方法必须有这样的async modifier/prefix
async doStuff(config: any) {
const answer1 = await this.service.fetchSth1(config).toPromise();
const answer2 = await this.service.fetchSth2(answer1).toPromise();
if (answer2) {
beHappy();
}
}
或者在登录的情况下,它现在可能看起来像这样
async login(name: string, pw: string) {
const token = await this.authService.getToken(name, pw).toPromise();
const passphrase = magic(name, pw, token);
const loginAnswer = await this.authService.login(name, passphrase).toPromise();
if (loginAnswer[`type`] === 'Success') {
doStuff();
}
}
我有一个带有登录方法和登录组件的身份验证服务。
从我的登录组件的 onSubmit 方法调用服务的登录方法并订阅答案。
具有挑战性的部分是,在该登录方法中,我必须执行多个成功的请求(一次调用获取用户名生成的令牌,然后使用该令牌加密输入的密码,然后再调用一次真正登录服务器)。不确定如何做到这一点,尝试了很多方法,但我对 angular 还是很陌生,而且在 Observable 方面也很不稳定。
登录组件:
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;
});
授权服务:
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;
}));
}
我尝试使用 http.get().pipe 及其许多变体进行多次嵌套调用,但显然我仍然缺乏一些基本的理解。
snippet/idea 来自这里:https://jasonwatmore.com/post/2019/06/10/angular-8-user-registration-and-login-example-tutorial
编辑:
我找到了解决方案,但它很丑陋,是否可以进行一些改进?
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();
});
当以顺序方式链接可观察对象时,使用了一个称为 higher-order-mapping 的概念(我建议 this article). one of these higher-order-mapping operators is concatMap and combining it with tap 运算符将帮助您以正确的方式实现您的目标。您的解决方案不仅丑陋:(它不可测试且不必要地复杂。您的 login
方法应该是这样的;
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);
}));
}));
}
我现在在进行后续调用时经常使用另一种模式。通常更干净。
封装方法必须有这样的async modifier/prefix
async doStuff(config: any) {
const answer1 = await this.service.fetchSth1(config).toPromise();
const answer2 = await this.service.fetchSth2(answer1).toPromise();
if (answer2) {
beHappy();
}
}
或者在登录的情况下,它现在可能看起来像这样
async login(name: string, pw: string) {
const token = await this.authService.getToken(name, pw).toPromise();
const passphrase = magic(name, pw, token);
const loginAnswer = await this.authService.login(name, passphrase).toPromise();
if (loginAnswer[`type`] === 'Success') {
doStuff();
}
}