Observables vs. Promises in Angular services

A short story

by Zlatko Đurić (@zladuric)

Boring details

document.innerHTML = '<ul class="userlist">';
for (var user in users) {
    document.innerHTML += '<li>' + user.name
       + '</li>';
}
document.innerHTML += '</ul>';
var userlist = document.createElement('ul');
for (var user of users) {
  var li = document.createElement('li');
  li.appendChild(document.createTextNode(user.name));
  node.appendChild(li);
}
document.body.appendChild(userlist);

Personal boring details

<== From this

To this ==>

The first snag

<header> Hello {{ user ? user.name : 'there' }}!</header>

this.user = authService.user$;

authService.login(username, pwd);

// doesn't do anything.

Authentication

Intercept? What?

localStorage.getItem('authToken');

// now what? $httpInterceptor?

AngularJS

function getUsers(username) {

     var qs = username ? '?username=' + username : '';

     var url = BASE_URL + qs;

     return $http.get(url)

        .then((response) => {

            // do some work here if needed

        });;

}

Angular 2

function getUsers(username?: string) {

   const qs = username ? `username=${username}` : '';

    const url = `${BASE_URL}${qs}`;

    return this.http.get(url)

        .map(res => res.json());

}

The Problem

Start simple


export class UserService {
  const BASE_URL: string = '/api/users';
  constructor (private http: Http) {}

  getUsers(username?: string) {
    const qs = username ? `username=${username}` : '';
    const url = `${BASE_URL}${qs};
    return this.http.get(url);
  }
}

Quiz time

userService.getUsers()?

Quiz time

userService.getUsers()? => nothing happens
userService.getUsers().subscribe(val => {
    console.log(val); // What is this?
})?

Quiz time

btw: userService.getUsers() => still nothing
userService.getUsers().subscribe(val => {
    console.log(val); // Raw stuff? Eh?
})?
userService.getUsers().subscribe(val => {
    console.log(val.json()); // This is how!
})?

Quiz time

for the record:
userService.getUsers() => where's my http call?
userService.getUsers().subscribe(val => {
    console.log(val);
    // GET 4.3!!! Defaults to res.json()!!!
})?
What when API returns 400?

Quiz time

userService.getUsers(); // Angular is not amused.
// also let me get this straight for prior to 4.3:
// who ever used raw?
userService.getUsers().subscribe(val => {
    console.log(val); // val === something I cannot read? Where's my .email?
})?
userService.getUsers().subscribe(
    val => handleSuccess(val),
    // You have to handle errors?
    err => handleError(err), 
);
What when there is no API?
btw:

Quiz time

userService.getUsers().subscribe(
    val => whatever,
    err => {
        if (err.status === 0) {
            alert('Server down.');
        } else { /* other errors */ }
    }    
})?
that was me a year ago:
userService.getUsers(); // WHY DON'T YOU WORK!!?

And the Promise version

export class UserService {
  const BASE_URL: string = '/api/users';
  constructor (private http: Http) {}

  getUsersAsPromise(username?: string) {
    const qs = username ? `username=${username}` : '';
    const url = `${BASE_URL}${qs};
    return this.http.get(url)
        .toPromise()
        .then(res => res.json());
  }
}

Usage

userService.getUsersAsPromise()

    .then(users => console.log(users))

    .catch(err => this.handleError(err));

About that Promise...

 
<my-filters>

    <input name="username">

    <input name="email">

    <button>Search</button>

</my-filters>

<!-- elsewhere on the page --->
<my-user-list>
    <ul>
       <li for="let user of users">{{ user.name }}</li>
    </ul>
</my-user-list>

 

Text

Quiz time

userService.getUsers(); // Nothing yet.

userService.getUsersAsPromise();  // The HTTP call is executed! Listening or not.

Promise chain vs that map thing

chainHttpCalls() {

    return this.http.get(url1)

        .toPromise()

        .then(val => {

           return this.http

              .get(url2 + val.id).toPromise()

       });       

}

chainHttpCalls() {

    return this.http.get(url1)

        .mergeMap(val => {

          return this.http.get(url2 + val.id)

       });

}

Promise.all vs that map thing

return Promise.all([

    this.http.get(url1).toPromise()

        .then(res => res.json(),

    this.http.get(url2).toPromise()

        .then(res => res.json(), 

]);
// use:

getAll()

    .then([result1, result2] => {

        console.log(result1, result2);

    });

return Observable.forkJoin([

    this.http.get(url1).map(res => res.json()),

    this.http.get(url2).map(res => res.json()),

]);

 

// use:

getAll()

    .subscribe([result1, result2] => {

        console.log(result1, result2);

    });

What if we don't care about errors?

return Promise.all([

    this.http.get(url1).toPromise()

        .then(res => res.json(),
           err => ({ state: 'rejected': reason: err.reason })),

 

    this.http.get(url2).toPromise()

        .then(res => res.json(), 

                     err => ({ state: 'rejected': reason: err.reason })),

]);
 

return Observable.forkJoin([

    this.http.get(url1).map(res => res.json())

        .catch(() => Observable.of({})),

    this.http.get(url2).map(res => res.json())

        .catch(() => Observable.of({})),

]);

 

Async Pipe Controversy

<div class="user-card">

    <p>{{ user.name | async }}</p>

    <p>{{ user.email | async }}</p>

</div>

 

// component...

this.user = this.http.get('/api/user').map(res => res.json());

// This gets called twice.

Share

<div class="user-card">

    <p>{{ user.name | async }}</p>

    <p>{{ user.email | async }}</p>

</div>


// component...

this.user = this.http.get('/api/user')

    .map(res => res.json())

    .share();

Share again?

<div class="user-card">

    <p>{{ user.name | async }}</p>

    <p>{{ user.email | async }}</p>

</div>

<button (click)="showMore = true">More</button>

<div *ngIf="showMore"> {{ user.phone | async }}</div>

 

// component...

this.user = this.http.get('/api/user')

    .map(res => res.json())

    .share();

// Again called second time on that showMore click

Share again!

<div class="user-card">

    <p>{{ user.name | async }}</p>

    <p>{{ user.email | async }}</p>

</div>

<button (click)="showMore = true">More</button>

<div *ngIf="showMore"> {{ user.phone | async }}</div>

 

// component...

this.user = this.http.get('/api/user')

    .map(res => res.json())

    .publishReplay();

A Promise to share

<div class="user-card">

    <p>{{ user.name | async }}</p>

    <p>{{ user.email | async }}</p>

</div>

<button (click)="showMore = true">More</button>

<div *ngIf="showMore"> {{ user.phone | async }}</div>

 

// component...

this.user = this.http.get('/api/user').toPromise()

    .then(res => res.json());

Interceptors and Headers

.factory('authInterceptor', function($rootScope, $window, $timeout) {
  return {
    request: function(config) {
      config.headers = config.headers || {};
      config.headers.Authorization = 'Bearer ' + sessionStorage.getItem('AuthToken');
      return config;
    },
    responseError: function(response) {
      if (response.status === 401) {
        $window.location = '/login.html?redirectUrl=' + $window.location;
      return throw response;
    }
  };
}).
config(['$httpProvider', function($httpProvider) {
  $httpProvider.interceptors.push('authInterceptor');
}]);

Interceptors and Headers

class AuthService {
    private headers: Headers = new Headers();
    constructor (private http: Http) { }
    login(username) {
        this.http.post('/api/login', `{"username": "${username}"}`)
            .map(res => {
                                this.headers.set('Authorization',
                `Bearer ${authority.access_token}`);
               return res.json();
            });
    }
    getOptions() {
        return { headers: this.headers };
    }
}

 

class UserService {
    constructor (private authService:AuthService, private http: Http) {}
    getUsers () {
        return this.http.get('/api/users', this.authService.getOptions)
            .map(res => res.json());
    }
}

What else?

  • Things like BehaviorSubject, Observable.interval
  • Operators like map, flatMap, switchMap, delay, retry...

Promises

Simple understandable semantics

Familiar 

Observables

this.autocomplete.nameChanges()
    .debounce(300)

    .retry(3)
    .distinctUntilChanged()

    .switchMap(

      this.users.find(name));
    

So which one?

Differences

  • Things are not that much different for basic use
  • Promises fire early, Observables lazy
  • You know when a promise fires off.
  • Observables are nicer for cross-component communication
  • Rxjs have a lot built-in functionality

I forgot some slides.

Observables vs. Promises in Angular services

By Zlatko Đurić

Observables vs. Promises in Angular services

  • 640