Служба наблюдаемых данных, подписка не вызывается

Я хочу создать реактивный магазин для пользовательского объекта. Итак, позвольте мне сначала описать, что я пробовал. Я определил простой пользовательский класс:

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() сначала извлекает идентификатор пользователя и запрашивает базу данных 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
);

подписка будет вызвана только один раз, когда пользователь будет нулевым. Почему это? this.user = user создает еще какую-то подписку и отменяет первую?


person Upvote    schedule 23.02.2017    source источник
comment
и что заставляет вас говорить, что пользователь снова не обновляется? Вы пытались зарегистрировать и назначить оба?   -  person Madhu Ranjan    schedule 23.02.2017
comment
@MadhuRanjan правильно, так как я регистрирую пользователя, и он регистрируется только один раз (ноль)   -  person Upvote    schedule 23.02.2017
comment
Можете ли вы создать плункер с вашей проблемой? Я попробовал, но не получил никаких проблем, проверьте этот Plunker, например, я просто б/у Observable.interval.   -  person Madhu Ranjan    schedule 23.02.2017


Ответы (1)


Проблема в том, что как минимум два разных значения выдаются вашим BehaviorSubject:

  • null — исходное значение, которое вы указали при создании экземпляра субъекта.
  • User instance — значение выдается сразу после возврата вызова Firebase.
  • [Необязательно] Модифицированный экземпляр User — поскольку все «живое» с Firebase, ваша тема может потенциально продолжать создавать разные экземпляры для текущего пользователя (например, вы получите новое значение, если пользователь выйдет из системы).

Таким образом, в зависимости от точки выполнения вашего .subscribe() вы можете получить значение null.

Вместо этого я бы попытался использовать ReplaySubject. ReplaySubject начинает генерировать только после того, как в него явно вставлено значение. Это означает, что вы можете избавиться от этого начального значения null, и пока Firebase не вернется, пользователь будет недоступен (= подписка будет «ожидать»).

В частности, вот строка, которую я бы изменил:

private _user: ReplaySubject<User> = new ReplaySubject<User>(1);
person AngularChef    schedule 24.02.2017
comment
Спасибо за Ваш ответ. Таким образом, BehaviorSubject используется для предоставления начального значения и потока значений с течением времени. Это отлично работает, например. список, который вначале пуст. Но для объекта не имеет смысла иметь начальное значение, верно? Итак, учитывая это, вы бы предложили использовать ReplaySubject? - person Upvote; 24.02.2017
comment
В яблочко! На самом деле вы все еще можете использовать ReplaySubject для предоставления значений с течением времени. Что отличает его от BehaviorSubject, так это то, что вы можете настроить размер буфера, то есть количество значений, которые он может воспроизвести. Но в вашем примере вы используете ReplaySubject не для этой функции (вы хотите воспроизвести только ОДНО значение), а для того факта, что он не сразу начинает выдавать значение по умолчанию. - person AngularChef; 24.02.2017