Handling nested observables

488 views Asked by At

I'm starting a project in Angular 2, and I'm dealing for the first time with Observables and have a few doubts for a very specific case.

I have an API service that goes like this:

@Injectable()
export class UserAPI {

  [...]

  login(email: string, password: string): Observable<Response> {
    let params = new URLSearchParams();
    params.set('email', email);
    params.set('password', password);

    return this.$http.request(
      new Request({
        method: RequestMethod.Post,
        url: this.baseUrl + '/login',
        search: params
      }))
      .map((response: Response) => response.json());
  }

  [...]
}

I'm managing the user session and its profile data (while its session is active) on a User service that includes a method that calls this API:

@Injectable()
export class UserService {

  [...]

  constructor(private userAPI: UserAPI, private storage: StorageService) {
  }

  login(email: string, password: string) {
    return this.userAPI.login(email, password).subscribe(
      (res) => {
        this.storage.store(Constants.storage.session, res.token);
      },
      (err) => {
        this.storage.destroy(Constants.storage.session);
      }
    );
  }

  [...]
}

Now, I also have a component that will handle the login form, and in case of success will redirect the user to a private area of the application, but if it fails, will present the with an error message (401, or any other error if by chance it happens):

@Component({
  moduleId: module.id,
  selector: "auth-login",
  templateUrl: "auth.login.html"
})
export class LoginComponent {

  constructor(private router: Router, private userService: UserService) {
  }

  [...]

  login(): void {
    this.form.loading = true;
    this.form.error = -100; // arbitrary number

    this.userService.login(this.form.email, this.form.password);
      /*
      .subscribe(
      (res) => {
        this.router.navigate(["/home", {}]);
      },
      (err) => {
        this.form.error = err.status;
      },
      () => {
        this.form.loading = false;
      }
    );
    */
  }

  [...]
}

Please note the commented code on userService.login() function call. Those are the instructions I intend to do with either the success/error of the call, but I can't get it to work. I'd like to keep things separated: the User service manages the session part and the component handles the redirection or shows the user what went wrong.

What I am doing wrong? What's the way to go here? Nested observables? Using promises between the component and the User service? Some other approach? I've been searching here on SO, but couldn't find a similar case to mine.

1

There are 1 answers

1
jonrsharpe On BEST ANSWER

I don't think you can chain subscriptions like that; you are effectively expecting thing.subscribe(...).subscribe(...) to work. Instead, you could use .map and .catch for the operations within the service, and let the component .subscribe:

login(email: string, password: string) {
  return this.userAPI.login(email, password)
      .map(res => {
        this.storage.store(Constants.storage.session, res.token);
        return res;
      })
      .catch(err => {
        this.storage.destroy(Constants.storage.session);
        return Observable.throw(err);
      });
}