$Q map to RxJS
AngularJS developers are familiar with $q and $http services to run functions asynchronously and execute remote calls . In most cases $q is used to retrieve data from http to render view. In this post I will try to implement “Angular 1 $q Promise” patterns with “Angular 2 RxJS Observable”. I assume that the reader is familiar with Angular 2 and Http service.
toPromise
Some people suggest to use toPromise any time you get observables and forget about Observables but I don’t think this is a good strategy in long term if you are serious about Angular 2. Here is an example of toPromise. Angular 2 Http service returns an Observable that is immediately converted to promise.
return this.http.get('url')
.toPromise()
.then(response => response.json().data)
.catch(this.handleError);
Not toPromise
Let’s try to keep observables. $q allows you to chain promises using “then” method. Observable operators are also chainable. Let’s try to rewrite simple promise chains using observables.
- Map. The first chain uses one http call, after multiple the response to 10 and assign the result to a view variable that is used to render.
$http({method: 'GET',url: '/someUrl'})
.then(data => ({data: data.value*10}))
.then(result => {this.view = result;})
.catch(error => {console.log(error);})
Analog of $http in Angular 2 is Http service that returns a Observable with raw response. We need to transform the response to JSON object, transform the result by multiplying to 10 and assigning the result to view variable. For simple transformation in RxJS “map” operator is used that takes a simple value and returns another simple value. To get the value from observable “subscribe” operator is used. So here is the version with Observable:
Http.get('/someUrl')
.map(rese => res.json().data)
.map(data => ({data: data.value*10})
.subscribe(result => {this.view = result;});
2. SwitchMap. Let’s try another example when we need to call one http service, analyze the response, call another http service and display data in view.
$http({method: 'GET',url: '/someUrl'})
.then(data => $http({method:'GET',url:`/otherUrl/${data.value}`}))
.then(result => {this.view = result;});
“Then” method can return a simple value but also another Promise. With Observable we have to use a flatterning operation when “map” operator returns an Observable. There are several flatterning operators in RxJS, the most save one for simple http is switchMap. SwitchMap returns an observable (Angular 2 http observable).
Http.get('/someUrl')
.map(rese => res.json().data)
.switchMap(data => Http.get(`/otherUrl/${data.value}`))
.subscribe(result => {this.view = result;});
3. ForkJoin. Another common case is $q.all. $q.all is used to create multiple http calls simultaneously and only when all requests get resolved, update the view with combine result.
$q.all[
$http({method: 'GET',url: '/someUrl'}),
$http({method: 'GET',url: '/otherUrl'})
]
.then(arr => {this.view = `${arr[0]} - ${arr[1]}`;});
Here is a version with Observables. forkJoin operator takes several observables (in our case http calls). The last argument of forkJoin is a function that accepts responses returned by all observables and allows to combine them to one object. Again we use subscribe to get result of observable chain.
Observable.forkJoin(
Http.get('/someUrl').map(res => res.json().data),
Http.get('/otherUrl').map(res => res.json().data),
(data1, data2) => ({data1, data2})
)
.subscribe(data => {this.view = `${data.data1} - ${data.data2}`;});
4. Of. In Angular 1 in order to create a resolved promise $q.when is used. It is often used to create a starter Promise. For example, if we already have a modal object we create a resolved Promise with model. Otherwise, we call http service to get a model
let promise = model ?
$q.when(model) :
$http({method: 'GET',url: '/modelUrl'})
The same login with Observable is:
let obs$ = model ?
Observable.of(model) :
Http.get('/modelUrl').map(res => res.json().data)
5. Empty the chain. It is was always an issue how to cancel the promise chain in the middle of executing. With Observable it is a simple “empty” operator. It completes the observable chain and we do not need to handle any errors.
Http.get('/someUrl').map(res => res.json().data)
.switchMap(result => result.value >0 ?
Observable.of(result) : Observable.empty()
)
.switchMap(() => Http.get('/otherUrl').map(res => res.json().data)};
We checked the value returned from the first http call and completed Observable with Observable.empty(). After complete, the next http was not fired.
6. Reject. $q provides a method to create a rejected promise.
$q.reject('Error')
RxJs has throw operator that does the same:
Observable.throw('Error')
7. Catch. With promise chains we use catch method to handle any exceptions and rejected promises.
$http({method: 'GET',url: '/someUrl'})
.then(data => $http({method:'GET',url:`/otherUrl/${data.value}`}))
.then(result => {this.view = result;})
.catch(err => console.log(err));
RxJS also has catch operator that is called when an error is happen during the execution and should return a new Observable to replace the error.
Replace with default object:
Observable.throw('Error')
.catch(err => {
console.log(err);
return Observable.of(defaultValue);
});
Complete observable:
Observable.throw('Error')
.catch(err => {
console.log(err);
return Observable.empty();
});
Throw another error:
Observable.throw('Error')
.catch(err => {
console.log(err);
return Observable.throw('Another Error');
});
8. Error handeling. Promise’s “then” accepts three functions. The second is a error handler:
$http({method: 'GET',url: '/someUrl'})
.then(
data => $http({method:'GET',url:`/otherUrl/${data.value}`}),
error=> {console.log(error);});
Also subscription operator provides an error function that can be used to handle errors:
Http.get('/someUrl').map(res => res.json().data)
.subscribe(
result => {this.view =result;},
error => { console.log(err);}
);
8. Finally. Finally method is used to perform an action when Promise or Observable resolved/reject/completed/errorred.
$http({method: 'GET',url: '/someUrl'})
.catch(err = > { console.log(err);})
.finally(()=> stopLoader());
Here is the same with RxJS
Http.get('/someUrl').map(res => res.json().data)
.catch(err = > { console.log(err);})
.finally(()=> stopLoader());
9. Do. “Then” is used in Promise to perform an effect. “Then” in this case should return an input argument.
$http({method: 'GET',url: '/someUrl'})
.then(result =>
{ console.log(result); return result})
.then(data => {this.view = data;});
RxJS provides a “do” operator that allows to perform an effect. The do operator should not return anything and the observable chin continues like “do” was not there.
Http.get('/someUrl').map(res => res.json().data)
.do(result => { console.log(result);})
.subscribe(data => {this.view = data;});
Conclusion
I hope it will help you not to do toPromise and enjoy Observables. When you get comfortable with these simple chains, you can start mixing observables from Ngrx, DOM, Angular 2 Form, Route..