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);      
      }));
    }));
  }

这里是上述概念的演示https://stackblitz.com/edit/angular-qvhay8

我现在在进行后续调用时经常使用另一种模式。通常更干净。

封装方法必须有这样的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();
    }
}