DarkLeafyGreen
DarkLeafyGreen

Reputation: 70416

Observable data service, subscribe not called

I want to create a reactive store for a user object. So let me first describe what I have tried. I defined a simple user class:

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

For simplicity the class just contains a key. I use firebase storage and to query this storage I created a service class:

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() first fetches the user id and queries the firebase database for the specific user. However I don't want to use the service directly. Rather I would like to have a global store that has a BehaviorSubject, which contains the latest user object. So lets introduce the store:

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();
  }
}

Now I use this store in a component like this:

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

And it works! The user is logged. However when I do something like

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

subscribe will be called just once with the user being null. Why is that? Does this.user = user create some kind of another subscription and cancels the first one?

Upvotes: 0

Views: 2962

Answers (1)

AngularChef
AngularChef

Reputation: 14087

The problem is that at least two different values are being emitted by your BehaviorSubject:

  • null — Initial value you provided when instantiating the subject.
  • User instance — Value emitted as soon as the Firebase call returns.
  • [Optionally] Modified User instance — Since everything is "live" with Firebase, your subject could potentially keep emitting different instances for the current user (for example, you'd get a new value if the user logged out).

So, depending on the point of execution of your .subscribe() you might end up getting the null value.

I would try to use a ReplaySubject instead. A ReplaySubject only starts emitting once a value is explicitly pushed into it. This means you can get rid of that initial null value and as long as Firebase hasn't returned, the user won't be available (= the subscribe will be "waiting").

More specifically, here's the line I'd change:

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

Upvotes: 4

Related Questions