6

I am new to Observable style programming. I have a question: I want to share user info across the app between component - and I use BehaviorSubject to share this info. This is inspired by sharing BehaviorSubject as AuthInfo. If I can share AuthInfo which contain a uid across my app component, why can I use it to share my user object data?

The problem is, the user object, I am getting that from Firebase. So it is an Observable, and I have no idea how can I assign this to a Behavior Subject. So here is the code I attempted:

  @Injectable()
  export class AuthService {

  static UNKNOWN_USER = new AuthInfo(null);

  static EMPTY_USER = new User(null, null, null, null);

  authInfo$: BehaviorSubject<AuthInfo> = new BehaviorSubject<AuthInfo>(AuthService.UNKNOWN_USER);

  userInfo$: BehaviorSubject<User> = new BehaviorSubject<User>(AuthService.EMPTY_USER);

  constructor(private auth: FirebaseAuth, private db:AngularFireDatabase, @Inject(FirebaseRef) fb) {

//firebase way to see if user is login or not
this.auth.subscribe(user => {
  if (user) {
    const authInfo = new AuthInfo(user.uid);
    this.authInfo$.next(authInfo);
    //This is the part I do not know how to do
    [OPTION 1]: this.userInfo$.next(findUserByuid(user.uid)) ?? - error
    [OPTION 2]: this.userInfo$ = findUserByuid(user.uid) ?? - userInfo$ turn into an observerable, which when I logout to call this.userInfo$.next(AuthService.EMPTY_USER) it will  throw error as .next() is not a function.
    [OPTION 3]:
    this.findUserByuid(user.uid).subscribe(user => {
      this.userInfo$.next(user);
    });
    ---- Can I put a subscribe inside of another subscribe to chain it like promise? I am not sure this is following the best practices. But this is the only option that there is no error. But just not sure I am doing it right. I do get the user object no problem

  }
  else {
    this.authInfo$.next(AuthService.UNKNOWN_USER);
  }
});


}

findUserByuid(uid:string):any {
   //return a firebase object observerable
   return this.db.object('userProfile/' + uid).map(user => User.fromJson(user));

}

logout() {

this.userInfo$.next(AuthService.EMPTY_USER);
this.authInfo$.next(AuthService.UNKNOWN_USER);
this.auth.logout();
}

And the bigger question, should I share data across application using behavior subject? Or should I just use observable like user$ = findUserByuid(uid) and make user$ as AuthService member variable. And then other components just subscribe to user$?

I am very confused on the best ways to share data across Angular2 WITHOUT using a dummy service like in the old way Angular 1. I saw ppl using Behavior Subject to share auth state - which is why I have all these questions and implementation. Any help will be greatly appreciated! There is no much resource out there regarding Behavior Subject.

[UPDATE]: The reason I choose behavior subject instead of just observable to pass data between different part of my app: Behavior Subject VS regular Observable

Community
  • 1
  • 1
Hugh Hou
  • 2,344
  • 5
  • 31
  • 55

2 Answers2

3

This is how I would write it:

...

authInfo$: Observable<AuthInfo>;
userInfo$: Observable<User>;

constructor(private auth: FirebaseAuth, private db:AngularFireDatabase, @Inject(FirebaseRef) fb) {
  //firebase way to see if user is login or not
  this.authInfo$ = auth
    .startWith(null) // in case auth has no initial value
    .map(user => user ? new AuthInfo(user.uid) : AuthService.UNKNOWN_USER)
    .publishReplay(1)
    .refCount();

  // automatically update the unserInfo$ when authInfo$ changes (no matter from where)
  // ...we are "reacting" on the data emitted by authInfo$ ... hence the name "reactive" programming
  this.userInfo$ = this.authInfo$
    .switchMap(authInfo => this.findUserByuid(authInfo.uid))
    .publishReplay(1);

  // this activates the whole stream-flow
  this.userInfo$.connect(); // instead of .connect() you could also append a ".refCount()" after the ".publishReplay(1)", however then it would only be acitve when there is at least 1 subscriber, so it depends on what you want
}

findUserByuid(uid?: string): Observable<User> {
  if (uid == null) {
    return Observable.of(AuthService.EMPTY_USER);
  } else {
    return this.db.object('userProfile/' + uid)
      .map(user => User.fromJson(user));
  }
}

logout() {
  this.auth.logout();
}

...
olsn
  • 16,644
  • 6
  • 59
  • 65
  • Hi @Olsn, so you are not suggesting to use Behavior Subject? The this.authInfo$ could be without an initial value and also other new components might not getting the latest value of authInfo$ and userInfo$ - as they are changing. That is why I need to use Behavior Subject. Could you explain why you only use Observable here? – Hugh Hou Jan 20 '17 at 16:53
  • Afaik `FirebaseAuth` is a ReplaySubject already, so it should work out of the box, if there is no initial value, you could use `.startWith(null)` - I'll update my code – olsn Jan 20 '17 at 17:16
  • Thanks. Coupled questions. How do you know FirebaseAuth is a ReplaySubject? Could you point me to the place that state that? And is data get using firebase.database.list and .object also ReplaySubject? I assume .publishReplay(1) .refCount(); you cache this.authInfo$. But authInfo$ change when user logout. So I lost that feature. How do I refresh caches when user logout and the value changed? And can this still change to behavior subject so I understand better - as I only learn example of behavior subject and not not sure about Repaly subject. Thanks! – Hugh Hou Jan 20 '17 at 18:46
  • Also, with your code is not working... this.userInfo$ is always empty. – Hugh Hou Jan 20 '17 at 19:34
  • 1
    It's right there in the docs: https://github.com/angular/angularfire2/blob/master/docs/api-reference.md#angularfireauth - and i'm guessing that userInfo is empty because the 'uid' is read out wrong from the AuthInfo class? I don't know your class so i just guessed that the uid could be accessed via authInfo.uid since you use it in the constructor – olsn Jan 21 '17 at 00:56
2

In my opinion the best way two share data across the application it's using the redux pattern that in angular 2 it's implement with ngrx/store plus ngrx/effects. As well as the smart compoment and presentation component pattern.

I have a example with those patterns

https://github.com/vigohe/ionic2-marvelApp

My running example:

https://enigmatic-plains-75996.herokuapp.com/

Official example from ngrx repo:

https://github.com/ngrx/example-app

More info:

Try to learn it because it will change your life ;)

Victor Godoy
  • 1,642
  • 15
  • 18
  • thank you so much for the suggestion. Funny thing is, I am learning ngrx/store via the paid angular university website on the chat app (included in one of your link). But it is still VERY HARD to wrap my brain around ngrx/store. One of my big issue is to use ngrx/store redux pattern with angularFire / Firebase - which is written in reactive programming style using observable. Do you have any resource to learn how to use ngrx/store with Firebase (Auth, CRUD, and other operation). I would love to use it if I can learn correctly HOW TO USE IT. Still tho, I am 100% agree with you. – Hugh Hou Jan 20 '17 at 16:45
  • Try to learn from the ngrx [example-app](https://github.com/ngrx/example-app) it's a great tutorial, you can also check this [course](https://egghead.io/courses/building-a-time-machine-with-angular-2-and-rxjs) from egghead.io For the crud part you need to learn the ngrx/effects – Victor Godoy Jan 20 '17 at 17:52