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