Jem
Jem

Reputation: 6406

Angular 4 & RxJS: race conditions, service initialisation order and readiness

I'd like to have confirmation that my use of observables vs. service initialisation is correct or isn't bested by a standard pattern. I'm new to observables.

Say that I have multiple services (e.g. profileService) depending on the readiness of an authService: Some data should only be loaded once a users is known.

The example profileService should only "obtain the connected user's profile" when the authentication service emits (observer.next('userConnected')) the signal that a user successfully authenticated to the app.

My profileService looks as follows:

constructor(@Inject(AuthenticationService) private authSvc: AuthenticationService){
        this.authSvc.getAuthChangeObservable().subscribe(data => this.onAuthenticationChanged(data) );
  }

While the authenticationService is written as follows:

private authChange: Observable<any>;
private _authObserver: Observer<any>;

constructor(@Inject(FirebaseService) firebaseService: FirebaseService){

    // Cookies or whatever allow .auth() to identify the user after a page refresh
    // This causes a slight delay, see my question below
    firebase.auth().onAuthStateChanged(user => {
      if(user) this.onAuthenticationSuccessfull(user);
      else this.onLogoutSuccessfull();
    });

    // Create an observable for auth status
    this.authChange = new Observable(observer => {
      this._authObserver = observer;
    }).share();
  }

  getAuthChangeObservable(){
    return this.authChange;
  }

...

onAuthenticationSuccessfull(user){
    this.authenticatedUser = user;

    // This is mainly the way I found to warn other services they are clear to load the
    // user-related data
    this._authObserver.next('auth');
}
...

What bothers me: What if there was no delay for authentication? Or in case of a more generic service, how to make sure that all the services depending on it are ready and subscribed to the observable?

I understand there is a race condition with observable.share(). Unless mistaken, whenever share() is called at least once, the first "pending" event is fired. And I need to call .share() as I need a "hot" observable (i.e. to not re-run the observable's observer .next() routine).

Thanks in advance for sharing advices and tips.

Upvotes: 2

Views: 1320

Answers (1)

Fan Cheung
Fan Cheung

Reputation: 11360

You can use Subject to simplify the code and it is a hot observable and by default it doesn't cache past event (the share behaviour you described is more like BehaviorSubject)

in your authenticationService

public onAuth=new Subject()
constructor(@Inject(FirebaseService) firebaseService: FirebaseService){

firebase.auth().onAuthStateChanged(user => {
  if(user) this.onAuth.next(user);
  else this.onLogoutSuccessfull();
})

in your profile service

constructor(@Inject(AuthenticationService) private authSvc: 
AuthenticationService){
// onlogin
    this.authSvc.onAuth.filter(user=>user&&user.id).map(data=>
// you can perform other operation here e.g chain, combine 
).subscribe()

// onlogout
    this.authSvc.onAuth.filter(user=>!user).map(data=>
// you can perform other operation here e.g chain, combine 
).subscribe()

}

Upvotes: 2

Related Questions