lostdorje
lostdorje

Reputation: 6460

Observable Chaining in Angular 2's new HttpClient

I have a simple and common scenario and I can't get my head around the correct way to do this with the new HttpClient. I'm coming from Angular 1 and chainable then() promises and not sure how to achieve this with Observables. (Would be great if Tour of Heroes tutorial was updated to reflect the shift onto HttpClient).

I have a main app.component.ts. This component will call a AuthenticationService.login() in authentication.service.ts.

AuthenticationService serves two purposes:

  1. It coordinates all client/server communication regarding authentication.
  2. It stores authentication information that can be shared across any and/or all components.

The code is ideally very simple (note: this is just contrived pseudocode)

app.component.ts:

export class AppComponent {
  loggedIn: boolean = false;

  constructor(private authenticationService: AuthenticationService) {
  }

  login() {
    // I want to do this but don't know how
    this.authenticationService.login(this.userLoginForm.email, this.userLoginForm.password).then(function(loggedIn) {
      this.loggedIn = loggedIn;
    }
  }
}

authentication.service.ts:

@Injectable()
export class AuthenticationService {
  user: User;

  constructor(private http: HttpClient) {
    // do stuff
  }

  // Recommendations I'm reading tend to prefer using Observables
  // instead of Promises. But I like the `then()` chaining that
  // I'm not sure how to do with Observables

  // Return what here? Observable<boolean> ?
  login(email, password): Observable<boolean> {
      const url = `http://127.0.0.1/token/auth`;

      // post() returns a LoginResponse that we subscribe to and handle here,
      // but I want to return a promise-like object that will resolve to
      // a boolean
      return this.http.post<LoginResponse>(url, {email: email, password: password})
          .subscribe(
            loginResponse => {
              if (loginResponse.error) {
                // set user to a new unauthenticated user
                this.user = new User();
              } else {
                this.user = JSON.parse(loginResponse.data.user) as User;
              }

              localStorage.setItem('currentUser', JSON.stringify(this.user));

              // returning a boolean here to the caller would be nice
              return this.user.id != null;
            }
          }
        );  
  );

}

What am I missing here? Isn't this trivial?

Arguably, there is no need to return a boolean here. app.component.ts can just read AuthenticationService.user to know if the user is logged in or not? Is that the way to do it?

More generally though, there surely must be a way for a service to process data returned from a server and then resolve promises that the calling component is waiting on. How does one do this?

Upvotes: 1

Views: 1485

Answers (2)

Faly
Faly

Reputation: 13346

You can use Obervable.map to transform things into what your service should return (you want an observable of boolean here). Use observable everywhere so change .then into .subscribe in your component.

// Your service:

@Injectable()
export class AuthenticationService {

    user: User;

    constructor(private http: HttpClient) {
    // do stuff
    }

    login(email, password): Observable<boolean> {

        const url = `http://127.0.0.1/token/auth`;

        return this.http.post<LoginResponse>(url, { email: email, password: password })
            .map(loginResponse => {
                this.user = loginResponse.error ? new User() : (JSON.parse(loginResponse.data.user) as User);
                localStorage.setItem('currentUser', JSON.stringify(this.user));
                return this.user.id != null;
            }
    }        
}

// Your component
export class AppComponent {

    loggedIn: boolean = false;

    constructor(private authenticationService: AuthenticationService) { }

    login() {
        this.authenticationService.login(this.userLoginForm.email, this.userLoginForm.password).subscribe(loggedIn =>  this.loggedIn = loggedIn);
    }
}

Upvotes: 1

Suren Srapyan
Suren Srapyan

Reputation: 68645

You can convert your Observable into the Promise using toPromise() function. Now your login will return Promise, not an Observable and you can chain it via thens.

login(email, password) : Promise {
      const url = `http://127.0.0.1/token/auth`;

      return this.http.post<LoginResponse>(url, {email: email, password: password})
          .toPromise().then(loginResponse => {
              this.user = loginResponse.error ? this.user = new User() :
                                                JSON.parse(loginResponse.data.user) as User;

              localStorage.setItem('currentUser', JSON.stringify(this.user));

              return this.user.id !== null;
          });  
}

Upvotes: 0

Related Questions