RxJS Observable & Observer issues

2019-09-09 09:26发布

问题:

I have an Angular 2 service that executes a few steps to authenticate and log in an app user. Any time I try to call next() on my Observer I get an error that it is undefined. The only place I can successfully call next() on it is inside contructor when the Observable is instantiated.

If I call authenticateUser() I get an error that this.isLoggedIn is undefined.

AuthService.ts

public isLoggedIn$: Observable<boolean>;
private isLoggedIn: Observer<boolean>;

constructor(...) {

    this.isLoggedIn$ = new Observable<boolean>(
        (observer: Observer<boolean>) => {
            this.isLoggedIn = observer;

            // this works fine
            this.doLogin();

        }).share()

}

private doLogin = ():void => {

    let context:AuthContextModel = this.authContextService.getAuthContext();

    if (context) {

        let isAuthenticated = this.isAuthenticated(context);

        if (isAuthenticated) {

            this.doCreateCurrentUserContext(context)
                .then((result) => {return this.doNotifyLoggedInStatus(result);});
        }
    }
};


private doNotifyLoggedInStatus = (result:boolean):Promise<boolean> => {

    this.isLoggedIn.next(result);

    return new Promise((resolve, reject) => {
        return resolve(true);
    });
};


public authenticateUser = (user: string, pass: string):Promise<boolean> => {
    return this.doFetchToken(user, pass)
        .then((fetchTokenData) => {return this.doStoreToken(fetchTokenData);})
        .then((authContext) => {return this.doCreateCurrentUserContext(authContext);})
        .then((result) => {return this.doNotifyLoggedInStatus(result);});

};

回答1:

If you want to call .next() on an Observer outside of the instantiation of the Observable, you should use a Subject rather than separate Observables and Observers. Subjects act as both. You can subscribe to them separately from where you pass events to it.

There are different kinds of Subjects, but for your situation, I would recommended a BehaviorSubject. Whenever you subscribe to it, not only will it catch future events, it will return the last event fired before the subscription happened. This is useful for an authentication component because you won't have to write separate code for a successful log in and actually checking if a user is authenticated after it has already happened (or a later sign out). It's really simple too.

import { BehaviorSubject } from 'rxjs/Rx'; 

//...
  public isLoggedIn: BehaviorSubject<boolean> = BehaviorSubject.create();

  constructor(...) { }

  private doLogin():void {

    //... Do the log in
    this.isLoggedIn.next(true);

  };

All you need to do wherever you want to check for log in status is grab the BehaviorSubject and subscribe to it. It doesn't matter whether or not the subscription happens before or after doLogin() was called.

If you intend to preserve the login state on refresh, all you'd need to do in the constructor is check for authentication and call the .next(true) on the BehaviorSubject as usual.

See also:

ReactiveX Subject docs

Intro to Rx BehaviorSubjects