Как подписаться на Angular 8 после завершения нескольких последовательных запросов?

У меня есть служба аутентификации с методом входа и компонентом входа.

Из метода onSubmit моих компонентов входа в систему я вызываю метод входа в службы и подписываюсь на ответ.

Сложность заключается в том, что в этом методе входа в систему мне нужно выполнить несколько последовательных запросов (один вызов, чтобы получить сгенерированный токен для имени пользователя, затем использовать этот токен для шифрования введенного пароля и сделать еще один вызов, чтобы затем действительно войти на сервер ). Не уверен, как это сделать, пробовал много подходов, но я все еще новичок в угловатости и шаткости, когда дело доходит до 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 и множество его вариантов, но, очевидно, мне все еще не хватает базового понимания.

Фрагмент/идея взята отсюда: 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();
});

person Allisone    schedule 11.07.2019    source источник


Ответы (2)


при последовательном объединении наблюдаемых используется концепция, называемая отображением более высокого порядка (я предлагаю эту статью). одним из этих операторов отображения более высокого порядка является concatMap, и его объединение с < оператор href="https://www.learnrxjs.io/operators/utility/do.html" rel="nofollow noreferrer">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

person ysf    schedule 12.07.2019

Теперь я часто использую другой шаблон, когда делаю последующие вызовы. Часто так намного чище.

Метод инкапсуляции должен иметь асинхронный модификатор/префикс, подобный этому

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();
    }
}
person Allisone    schedule 10.12.2019