Angular 7 - 使用 BehaviorSubject 向所有组件发布 API 结果

Angular 7 - Use BehaviorSubject for Publishing API Results to All Components

我有一个 API 调用 returns 当前用户的数据(即用户名、全名、授权组成员身份、电子邮件地址等)。我只想在每个用户会话中调用此 API 一次,并在所有组件之间共享其数据。

我不想依赖 localStorage,因为我不希望用户可以修改这些数据。我正在尝试使用 BehaviorSubject 并尝试遵循许多示例,但在尝试通过我的组件访问它时,我一直收到初始值 (null)。

// HTTP Service:
getUserData() {
  const url = `${baseUrl}/api/users/`;
  return this.httpClient.get<UserData>(url).toPromise();
}
// Utils Service
export class UtilsService {
  private userDataSubject: BehaviorSubject<UserData>;

  constructor(
    private httpService: HttpService,
  ) {
    this.userDataSubject = new BehaviorSubject<UserData>(null);
    this.setUserData();
  }

  async setUserData() {
    let userData: UserData = await this.httpService.getUserData();
    this.userDataSubject.next(userData);
    console.log(this.userDataSubject.value); // Properly returns the data I want     
  }

  public get userDataValue(): UserData {
    return this.userDataSubject.value;
  }
// Component
  ngOnInit() {
    this.userData = this.utils.userDataValue; // This is the variable I need to set in my component.
    console.log(this.userData);               // Returns null
    console.log(this.utils.userDataValue);    // Also returns null
  }

我也尝试过为 userDataValue() 使用异步函数:

async userDataValue(): Promise<UserData> {
    return this.userDataSubject.value;
  }

并修改了我的组件:

async ngOnInit() {
    this.userData = await this.utils.userDataValue();
    console.log(this.userData);                    // Still returns null
    console.log(await this.utils.userDataValue()); // Still returns null
  }

到目前为止,我只能在避免使用 BehaviorSubject 并在每个组件中调用 API 的情况下才能完成这项工作,但这似乎没有必要。我将非常感谢任何有关如何完成这项工作的指导。谢谢。

问题是 setUserData() 是一个异步函数,this.httpService.getUserData() 需要一些时间才能从后端获取数据。因此,当您的组件调用它 ngOnInit() 方法时,userDataSubject 尚未收到新值,这就是它 returns 为空的原因。我建议使用 BehaviorSubject subscribe 处理程序,这样您的代码就会在正确的时间执行它应该执行的操作。它会是这样的:

export class UtilsService {
  public userDataSubject: BehaviorSubject<UserData>;

  constructor(private httpService: HttpService) {
    this.userDataSubject = new BehaviorSubject<UserData>(null);
    this.setUserData();
  }

  async setUserData() {
    let userData: UserData = await this.httpService.getUserData();
    this.userDataSubject.next(userData);     
  }

并在您的组件中

ngOnInit() {
  this.utils.userDataSubject.subscribe((data) => {
    if(data === null)
      return;

    console.log(data);

     // component logic to use your data
     // this will be called every time userDataSubject receives a new value
  });
}

这里是的一点改进:

您应该使用尺寸 1 的 ReplaySubject<> 而不是 BehaviorSubject<>。为什么?
BehaviorSubject 必须用一个值初始化。在您的代码中,它是 null。这意味着:所有订阅者都必须处理 null(就像在 alexortizl 的回答中,if(date === null) 行)。

另一方面,大小为 1 的 ReplaySubject 将像 BehaviorSubject 一样工作,但没有初始值。在发出第一个正确的值之前,所有订阅者都不会获得任何值。阅读本文以进行更深入的研究 ->

因此,您的代码将如下所示:

服务

export class UtilsService {
  // we set the type to "Subject" to hide the actual ReplaySubject 
  public userDataSubject$: Subject<UserData> = new ReplaySubject(1);

  constructor(private httpService: HttpService) {
    this.setUserData();
  }

  async setUserData() {
    let userData: UserData = await this.httpService.getUserData();
    this.userDataSubject$.next(userData);     
  }

Component.ts

ngOnInit() {
  this.utils.userDataSubject$.subscribe((data) => {
    console.log(data);
     // component logic to use your data
     // this will be called every time userDataSubject receives a new value
  });
}

Component.html

... 或使用 async-管道更好。然后你不必取消订阅,它可以提高 Angular 的更改检测。深潜 -> “SEEING” ANGULAR CHANGE DETECTION IN ACTION

<ng-container *ngIf="utils.userDataSubject$ | async as userData">
    <span>Hello {{ userData.firstName }} {{ userData.lastName }}</span>
</ng-container>