Search code examples
angularservicepipeobservablereload

Angular/Ionic RXJS pipe/map undefined element on reload


Simple Admin Role checking method:

 isAdmin() {
    return this.userProfileObservable.pipe(map((profile: UserObject) => {
//Here I can place some delaying code and it works!!!
        return profile != null ? profile.role === 'admin' : false;
    }));
}

Works perfectly fine if I navigate to that page from prev page and Router call AdminGuard:

 canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> {
    return this.auth.isAdmin();
}

Observables are initialised in service constructor:

private _userProfileSubject: BehaviorSubject<UserObject>;
private _firebaseUserSubject: BehaviorSubject<firebase.User>;
private userProfileCollection: AngularFirestoreDocument<UserObject>;
public userProfileObservable: Observable<UserObject>;
private authObserver: firebase.Unsubscribe;

constructor(
    private afAuth: AngularFireAuth,
    private afs: AngularFirestore, ) {
    // Init observers with null initial value
    this._userProfileSubject = new BehaviorSubject(new UserObject()) as BehaviorSubject<UserObject>;
    this._firebaseUserSubject = new BehaviorSubject(null) as BehaviorSubject<firebase.User>;
    this.userProfileObservable = this._userProfileSubject.asObservable();

    this.authObserver = afAuth.auth.onAuthStateChanged((user) => {
        this._firebaseUserSubject.next(user);

        if (user == null) {
            this._userProfileSubject.next(null);
            return;
        }
        this.userProfileCollection = afs.collection<UserObject>('Users').doc(user.uid);
        // Monitor auth changes and update behaviorSubject -> observable <UserObject>
        this.userProfileCollection.snapshotChanges().subscribe((action => {
            const data = action.payload.data() as UserObject;
            data.uid = action.payload.id;
            this._userProfileSubject.next(data);
            return data;
        }));
    });
}

But if I reload page / navigate directly to route protected by AdminGuard, property/element in pipe -> map -> "profile" becomes undefined for some reason.

Its like not everything is yet loaded, as if I perform "BIG HACK" and await/delay before in isAdmin method, then it works again...


Solution

  • Can you please make the following changes and try:

    In your service lets initialize BehavorSubject with null;

    this._userProfileSubject: BehaviorSubject<UserObject> = new BehaviorSubject(null);
    

    Then update your isAdmin() method such that it will ignore very first 'null' value of userProfileObservable and wait until it emits NOT NULL value [to do that use skipWhile() operator] like this:

    isAdmin() {
                return this.userProfileObservable
                           .pipe(
                            //it will skip all null value until source sobservable emits a NOT NULL value; i.e. it will wait until your service's subscribe get the response and emits a NOT NULL value of UserObject
                            skipWhile(u => !u),
                            map((profile: UserObject) => {
                              //Here I can place some delaying code and it works!!!
                              return profile != null ? profile.role === 'admin' : false;
                            })
                          );
            }