Learner
Learner

Reputation: 571

Emit Observable value and handle it in component

I have a login() method an AuthService. What I want is to emit value from login() method in AuthService to handle value in NavbarComponent:

export class AuthService extends AbstractRestService<any> {
    user: AppUser = {id: null}; 

    login(userName: string, password: string) {
        return this.http
            .post('/auth/login')
                .pipe(
                    map(res => {
                    // here I want to emit value of Observable `anUser$` 
                    // to send value to NavbarComponent
                        this.getAppUser(); // I want to throw Observable value to handle its value 
                                           // in NavbarComponent.ts         

                        return true;
                  })
                 , catchError(this.handleError)
            );
    }

    public getAppUser(): Observable<AppUser> {   
        return of(this.user);

    }
}

My NavbarComponent.html looks like this:

<li *ngIf="appUser$ | async as anUser">

 {{ anUser?.name }}

</li>

My NavbarComponent.ts looks like this:

appUser$: Observable<AppUser>;

async ngOnInit() {
    this.appUser$ = this.auth.getAppUser();    
}

What I want is to emit value from login() method in AuthService to handle value in NavbarComponent, however value of appUser$ is not shown in NavbarComponent.html.

Could you tell me what I am doing wrong?

Upvotes: 0

Views: 425

Answers (2)

Daniel Gimenez
Daniel Gimenez

Reputation: 20454

By returning of(this.user) from the method getUser() all you're doing is returning the an observable with a single emission that is the value of this.user at the time of the call.

If you want a stream that will be updated every time the user is updated by login then the best way to do that would be to use a Subject. A Subject is an Observable that whose emissions can be set from the outside.

You probably want to use a special subject called BehaviorSubject that will emit the last value as soon as it is subscribed too. This way if login was already called after the observable is subscribed to then the current value will still be returned. It is equivalent to a regular observable with startWith and shareReplay operator.

export class AuthService extends AbstractRestService<any> {
  private readonly userSubject = new BehaviorSubject<{ id: string }>();



  login(userName: string, password: string) {
    return this.http
      .post('/auth/login')
      .pipe(
         tap(res => userSubject.next(res)),
         catchError(this.handleError)
      );
  }

  getAppUser() {
    /** do other work **/
    return this.userSubject.asObservable();
  }
}

Personally, I would just assign the variable directly by exposing userSubject as a readonly field using this.userSubject.asObservable(). Since you indicated you have additional logic occurring in getAppUser(), you can just do the same call in the method.

Upvotes: 3

bryan60
bryan60

Reputation: 29315

do like this:

export class AuthService extends AbstractRestService<any> {
    user: AppUser = {id: null}; 

    login(userName: string, password: string) {
        return this.http
            .post('/auth/login')
                .pipe(
                    map(res => {
                        this.userSource.next(res); // just call next with the user here       

                        return true;
                  })
                 , catchError(this.handleError)
            );
    }

    private userSource = new BehaviorSubject<AppUser>({id: null})
    public getAppUser = this.userSource.asObservbale();
}

Upvotes: 3

Related Questions