如何通过对话框拦截未经授权的响应以输入密码并重试?

How do I intercept an unauthorised response with a dialog to enter a password and retry it?

我正在构建一个应用程序,该应用程序的后端具有简单的身份验证和 GET 请求。我想向用户显示一个对话框,让他们在身份验证过期时重新输入密码。当您填写密码并点击提交时,我想重试原来的 HTTP 调用并获取原始 Observable 内的数据。我猜这是可能的,但我似乎无法找到开始的方法。

这个问题类似:

但我正在使用 .pipe()catchError,因此我可以在我的 API 服务中订阅该功能,而不是单独的 http 调用。我不确定我应该提供哪些其他信息来使我的问题更清楚,但如果您还有其他需要了解的信息,请告诉我。

这是我当前的代码:

import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { environment } from "../environments/environment";
import { Observable, ObservableInput } from "rxjs/Observable";
import { catchError, tap } from "rxjs/Operators";
import { MatDialog } from "@angular/material/dialog";
import { SessionExpiredComponent } from "./dialogs/session-expired/session-expired.component";
import "rxjs/add/observable/throw";
import { User } from "./classes/user";
import { Test } from "./classes/test";

const header = {
  withCredentials: true
};

@Injectable()
export class ApiService {
  user: User = null || JSON.parse(localStorage.getItem("user"));

  constructor(private http: HttpClient, private matDialog: MatDialog) {}

  private errorHandler() {
    return (err: any) => {
      let dialog = this.matDialog.open(SessionExpiredComponent, {
        data: { err: err, email: this.user.email, apiService: this },
      });
      return dialog.afterClosed();
//I want to return a retry of the original observable
    };
  }

  login(email: string, password: string): void {
    const formHeader = {
      headers: new HttpHeaders({
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
      }),
      withCredentials: true
    };
    this.http
      .post<User>(
        environment.backend + "auth/login",
        "email=" +
          encodeURIComponent(email) +
          "&password=" +
          encodeURIComponent(password),
        formHeader
      )
      .subscribe(user => {
        this.user = user;
        localStorage.setItem("user", JSON.stringify(user));
      });
  }

  getTests(): Observable<any> {
    return this.http
      .get<any>(
        environment.backend +
          "tests/query?accountId=" +
          this.user.defaultAccount,
        header
      )
      .pipe(catchError(this.errorHandler()));
  }
}

此代码按原样工作,但在我输入密码并重新验证时不会重试调用。

我明白了。通过使用 .flatMap(),我可以 link 一个 Observable 紧接着另一个 Observable。这意味着 errorHandler 看起来像这样:

private errorHandler(source: Observable<any>) {
    return (err: any) => {
      let dialog = this.matDialog.open(SessionExpiredComponent, {
        data: { err: err, email: this.user.email, apiService: this }
      });
      return dialog.afterClosed().flatMap(data => {
        return source.retry();
      });
    };
  }

对话框关闭后,returns 重试源 Observable,我需要像这样将其传递到 errorHandler

  getTests(): Observable<any> {
    let observable = this.http
      .get<any>(
        environment.backend +
          "tests/query?accountId=" +
          this.user.defaultAccount,
        header
      );
      return observable.pipe(catchError(this.errorHandler(observable)));
  }

现在我的 getTests 函数是一个 Observable,它将:

  • 获取测试
  • 如果失败,显示密码对话框以重新连接
  • 对话完成后(这意味着我 re-authenticated),重试 HTTP 调用