【问题标题】:Observable data service, subscribe not called可观察数据服务,订阅未调用
【发布时间】:2026-01-28 19:25:01
【问题描述】:

我想为用户对象创建一个反应式存储。所以让我先描述一下我尝试过的东西。我定义了一个简单的用户类:

export class User {
  constructor(public $key: string) {}
}

为简单起见,该类仅包含一个键。我使用 firebase 存储并查询此存储,我创建了一个服务类:

export class UserService {

  constructor(private af: AngularFire, private authService: AuthService) {}

  getUser() {
    return this.authService.authInfo$.concatMap(a => this.af.database.object('/users/' + a.uid));
  }
}

getUser() 首先获取用户 ID 并查询特定用户的 firebase 数据库。但是我不想直接使用该服务。相反,我希望有一个具有 BehaviorSubject 的全局存储,其中包含最新的用户对象。那么让我们介绍一下商店:

export class UserStore {

  private _user: BehaviorSubject<User> = new BehaviorSubject(null);

  constructor(private userService: UserService) {
    this.userService.getUser().subscribe(
      userJson => {
        this._user.next(new User(userJson.$key, userJson.firstName, userJson.lastName, null, userJson.personalDataSheet));
      },
      err => console.error
    );
  }

  get user() {
    return this._user.asObservable();
  }
}

现在我在这样的组件中使用这个商店:

this.userStore.user.subscribe(
  user => console.log(user)
);

而且它有效!用户已登录。但是,当我做类似的事情时

user: User;
...
this.userStore.user.subscribe(
  user => this.user = user
);

subscribe 只会在用户为 null 的情况下被调用一次。这是为什么? this.user = user 是否会创建某种其他订阅并取消第一个订阅?

【问题讨论】:

  • 是什么让你说用户没有再次更新?您是否尝试过记录并分配两者?
  • @MadhuRanjan 对,因为我登录用户并且它只记录一次(空)
  • 你能创建一个 Plunker 来处理你的问题吗?我试过了,但没有遇到任何问题,请查看Plunker,例如,我刚刚使用了Observable.interval

标签: angular rxjs angular2-observables


【解决方案1】:

问题在于您的 BehaviorSubject 发出了至少两个不同的值

  • null — 实例化主题时提供的初始值。
  • User instance — Firebase 调用返回后立即发出的值。
  • [可选] 修改 User 实例 - 由于 Firebase 的一切都是“实时”的,因此您的主题可能会继续为当前用户发出不同的实例(例如,如果用户注销,您将获得一个新值) .

因此,根据您的.subscribe() 的执行点,您最终可能会获得null 值。

我会尝试改用 ReplaySubject。 ReplaySubject 只有在将值显式推入其中后才开始发射。这意味着您可以去掉最初的 null 值,只要 Firebase 没有返回,用户将不可用(= 订阅将“等待”)。

更具体地说,这是我要更改的行:

private _user: ReplaySubject<User> = new ReplaySubject<User>(1);

【讨论】:

  • 感谢您的回答。因此 BehaviorSubject 用于提供初始值和一段时间内的值流。这适用于例如一个列表,一开始是空的。但是对于一个对象来说,拥有一个初始值真的没有意义吗?所以考虑到这一点,你会建议使用 ReplaySubject?
  • 没错!实际上,您仍然可以使用 ReplaySubject 随着时间的推移提供值。它与 BehaviorSubject 的不同之处在于您可以调整“缓冲区大小”,即它可以重放的值的数量。但是在您的示例中,您没有将 ReplaySubject 用于此功能(您只想“重播” ONE 值),而是因为它不会立即开始发出默认值。