Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to easily convert or assign an Observable to a Behavior Subject, so other component can share it

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

like image 625
Hugh Hou Avatar asked Jan 20 '17 04:01

Hugh Hou


2 Answers

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

...
like image 67
olsn Avatar answered Oct 30 '22 07:10

olsn


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:

  • https://github.com/ngrx/store
  • http://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/

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

like image 34
Victor Godoy Avatar answered Oct 30 '22 07:10

Victor Godoy