MyPage is a personalized page based on your interests.The page is customized to help you to find content that matters you the most.


I'm not curious

Get Your Angular 2 On: Upgrading from 1.5

Published on 24 June 16
1749
0
2

I started out wanting to write a step-by-step guide for upgrading an app from Angular 1.5 to Angular 2, before I was politely informed by my editor that she needed an article rather than a novel. After much deliberation, I accepted that I needed to start with a broad survey of the changes in Angular 2, hitting all the points covered in Jason Aden’s Getting Past Hello World in Angular 2 article. …Oops. Go ahead and read it to get an overview of Angular 2’s new features, but for a hands-on approach keep your browser right here.

I want this to become a series that eventually encompasses the entire process of upgrading our demo app to Angular 2. For now, though, let’s start with a single service. Let’s take a meandering walk through the code and I’ll answer any questions you may have, such as….

‘OH NO WHY IS EVERYTHING SO DIFFERENT’

Angular: The Old Way

If you’re like me, the Angular 2 quickstart guide might have been the first time you ever looked at TypeScript. Real quickly, according to its own website, TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. You install the transpiler (similar to Babel or Traceur) and you wind up with a magical language that supports ES2015 & ES2016 language features as well as strong typing.

You may find it reassuring to know that none of this arcane setup is strictly necessary. It’s not terribly difficult to write Angular 2 code in plain old JavaScript, although I don’t think it’s worth it to do so. It’s nice to recognize familiar territory, but so much of what’s new and exciting about Angular 2 is its new way of thinking rather than its new architecture.

Get Your Angular 2 On: Upgrading from 1.5 - Image 1What’s new and exciting about Angular 2 is its new way of thinking rather than its new architecture.

So let’s look at this service that I upgraded from Angular 1.5 to 2.0.0-beta.17. It’s a fairly standard Angular 1.x service, with just a couple of interesting features that I tried to note in the comments. It’s a bit more complicated than your standard toy application, but all it’s really doing is querying Zilyo, a freely-available APIthat aggregates listings from rental providers like Airbnb. Sorry, it’s quite a bit of code.

zilyo.service.js (1.5.5)

'use strict'; function zilyoService($http, $filter, $q) { // it's a singleton, so set up some instance and static variables in the same place var baseUrl = "https://zilyo.p.mashape.com/search"; var countUrl = "https://zilyo.p.mashape.com/count"; var state = { callbacks: {}, params: {} }; // interesting function - send the parameters to the server and ask // how many pages of results there will be, then process them in handleCount function get(params, callbacks) { // set up the state object  if (params) { state.params = params; } if (callbacks) { state.callbacks = callbacks; } // get a count of the number of pages of search results return $http.get(countUrl + "?" + parameterize(state.params)) .then(extractData, handleError) .then(handleCount); } // make the factory return { get : get }; // boring function - takes an object of URL query params and stringifies them function parameterize(params) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } // interesting function - takes the results of the "count" AJAX call and // spins off a call for each results page - notice the unpleasant imperativeness function handleCount(response) { var pages = response.data.result.totalPages; if (typeof state.callbacks.onCountResults === "function") { state.callbacks.onCountResults(response.data); } // request each page var requests = _.times(pages, function (i) { var params = Object.assign({}, { page : i + 1 }, state.params); return fetch(baseUrl, params); }); // and wrap all requests in a promise return $q.all(requests).then(function (response) { if (typeof state.callbacks.onCompleted === "function") { state.callbacks.onCompleted(response); } return response; }); } // interesting function - fetch an individual page of results // notice how a special callback is required because the $q.all wrapper // will only return once ALL pages have been fetched function fetch(url, params) { return $http.get(url + "?" + parameterize(params)).then(function(response) { if (typeof state.callbacks.onFetchPage == "function") { // emit each page as it arrives state.callbacks.onFetchPage(response.data); } return response.data; // took me 15 minutes to realize I needed this }, (response) => console.log(response)); } // boring function - takes the result object and makes sure it's defined function extractData(res) { return res || { }; } // boring function - log errors, provide teaser for greater ambitions function handleError (error) { // In a real world app, we might send the error to remote logging infrastructure var errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return errMsg; } } // register the service angular.module('angularZilyoApp').factory('zilyoService', zilyoService); 

The wrinkle in this particular app is that it shows results on a map. Other services handle multiple pages of results by implementing pagination or lazy scrollers, which allows them to retrieve one neat page of results at a time. However, we want to show all results within the search area, and we want them to appear as soon as they return from the server rather than suddenly appearing once all pages are loaded. Additionally, we want to display progress updates to the user so that they have some idea of what’s happening.

In order to accomplish this in Angular 1.5, we resort to callbacks. Promises get us partway there, as you can see from the $q.all wrapper that triggers the onCompleted callback, but things still get pretty messy.

Then we bring in lodash to create all of the page requests for us, and each request is responsible for executing the onFetchPage callback to make sure that it’s added to the map as soon as it’s available. But that gets complicated. As you can see from the comments, I got lost in my own logic and couldn’t get a handle on what was being returned to which promise when.

The overall neatness of the code suffers even more (far more than is strictly necessary), because once I become confused, it only spirals downward from there. Say it with me, please…

‘THERE HAS TO BE A BETTER WAY’

Angular 2: A New Way of Thinking

There is a better way, and I’m going to show it to you. I’m not going to spend too much time on the ES6 (a.k.a. ES2015) concepts, because there are far better places to learn about that stuff, and if you need a jumping-off point, ES6-Features.org has a good overview of all of the fun new features. Consider this updated AngularJS 2 code.

zilyo.service.ts (2.0.0-beta.17)

import {Injectable} from 'angular2/core'; import {Http, Response, Headers, RequestOptions} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; @Injectable() export class ZilyoService { constructor(private http: Http) {} private _searchUrl = "https://zilyo.p.mashape.com/search"; private _countUrl = "https://zilyo.p.mashape.com/count"; private parameterize(params: {}) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } get(params: {}, onCountResults) { return this.http.get(this._countUrl, { search: this.parameterize(params) }) .map(this.extractData) .map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; }) .flatMap(results => Observable.range(1, results.totalPages)) .flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); }) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); } let body = res.json(); return body.result || { }; } private handleError (error: any) { // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return Observable.throw(errMsg); } } 

Cool! Let’s walk through this line by line. Again, the TypeScript transpiler lets us use any ES6 features that we want because it converts everything to vanilla JavaScript.

The import statements at the beginning are simply using ES6 to load in the modules that we need. Since I do most of my development in ES5 (aka regular JavaScript), I must admit that it’s a bit annoying to suddenly need to start listing every object that I plan to use.

However, keep in mind that TypeScript is transpiling everything down to JavaScript and is secretly usingSystemJS to handle module loading. The dependencies are all being loaded in asynchronously, and it is (allegedly) able to bundle your code in a way that strips out symbols that you haven’t imported. Plus it all supports aggressive minification, which sounds very painful. Those import statements are a small price to pay to avoid dealing with all of that noise.

Get Your Angular 2 On: Upgrading from 1.5 - Image 5Import statements are a small price to pay for what's going on behind the scenes.

Anyway, apart from loading in selective features from Angular 2 itself, take special notice of the line import {Observable} from 'rxjs/Observable';. RxJS is a mind-bending, crazy-cool reactive programming library that provides some of the infrastructure underlying Angular 2. We will definitely be hearing from it later.

Now we come to @Injectable().

I’m still not totally sure what that does to be honest, but the beauty of declarative programming is that we don’t always need to understand the details. It’s called a decorator, which is a fancy TypeScript construct capable of applying properties to the class (or other object) that follows it. In this case, @Injectable() teaches our service how to be injected into a component. The best demonstration comes straight from the horse’s mouth, but it’s quite long so here’s a sneak peek of how it looks in our AppComponent:

@Component({ ... providers: [HTTP_PROVIDERS, ..., ZilyoService] }) 

Next up is the class definition itself. It has an export statement before it, which means, you guessed it, we can import our service into another file. In practice, we’ll be importing our service into our AppComponentcomponent, as above.

Get Your Angular 2 On: Upgrading from 1.5 - Image 6@Injectable() teaches our service how to be injected into a component.

Right after that is the constructor, where you can see some real dependency injection in action. The line constructor(private http:Http) {} adds a private instance variable named http that TypeScript magically recognizes as an instance of the Http service. Point goes to TypeScript!

After that, it’s just some regular-looking instance variables and a utility function before we get to the real meat and potatoes, the get function. Here we see Http in action. It looks a lot like Angular 1’s promise-based approach, but under the hood it’s way cooler. Being built on RxJS means that we get a couple of big advantages over promises:

  • We can cancel the Observable if we no longer care about the response. This might be the case if we’re building a typeahead autocomplete field, and no longer care about the results for ca once they’ve entered cat.
  • The Observable can emit multiple values and the subscriber will be called over and over to consume them as they are produced.

The first one is great in a lot of circumstances, but it’s the second that we’re focusing on in our new service. Let’s go through the get function line by line:

return this.http.get(this._countUrl, { search: this.parameterize(params) }) 

It looks pretty similar to the promise-based HTTP call you would see in Angular 1. In this case, we’re sending the query parameters to get a count of all matching results.

.map(this.extractData) 

Once the AJAX call returns, it will send the response down the stream. The method map is conceptually similar to an array’s map function, but it also behaves like a promise’s then method because it waits for whatever was happening upstream to complete, regardless of synchronicity or asynchronicity. In this case, it simply accepts the response object and teases out the JSON data to pass downstream. Now we have:

.map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; }) 

We still have one awkward callback that we need to slide in there. See, it’s not all magic, but we can process onCountResults as soon the AJAX call returns, all without leaving our stream. That’s not too bad. As for the next line:

.flatMap(results => Observable.range(1, results.totalPages))

Uh oh, can you feel it? A subtle hush has come over the onlooking crowd, and you can tell that something major is about to happen. What does this line even mean? The right-hand part isn’t that crazy. It creates an RxJS range, which I think of as a glorified Observable-wrapped array. If results.totalPages equals 5, you end up with something like Observable.of([1,2,3,4,5]).

flatMap is, wait for it, a combination of flatten and map. There’s a great video explaining the concept at Egghead.io, but my strategy is to think of every Observable as an array. Observable.range creates its own wrapper, leaving us with the 2-dimensional array [[1,2,3,4,5]]. flatMap flattens the outer array, leaving us with [1,2,3,4,5], then map simply maps over the array, passing the values downstream one at a time. So this line accepts an integer (totalPages) and converts it into a stream of integers from 1 to totalPages. It may not seem like much, but that’s all we need to set up.

THE PRESTIGE

I really wanted to get this on one line to increase its impact, but I guess you can’t win them all. Here we see what happens to the stream of integers that we set up on the last line. They flow into this step one by one, then are added to the query as a page parameter before finally being packaged into a brand new AJAX request and sent off to fetch a page of results. Here’s that code:

.flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); }) 

If totalPages was 5, we construct 5 GET requests and send them all off simultaneously. flatMap subscribes to each new Observable, so when the requests return (in any order) they are unwrapped and each response (like a page of results) is pushed downstream one at a time.

Let’s look at how this whole thing works from another angle. From our originating count request, we find the total number of pages of results. We create a new AJAX request for each page, and no matter when they return (or in what order), they are pushed out into the stream as soon as they are ready. All that our component needs to do is subscribe to the Observable returned by our get method, and it will receive each page, one after the other, all from a single stream. Take that, promises.

Get Your Angular 2 On: Upgrading from 1.5 - Image 7The component will receive each page, one after the other, all from a single stream.

It’s all a bit anti-climactic after that:

.map(this.extractData).catch(this.handleError); 

As each response object arrives from the flatMap, its JSON is extracted in the same manner as the response from the count request. Tacked on to the end there is the catch operator, which helps illustrate how stream-based RxJS error handling works. It’s pretty similar to the traditional try/catch paradigm, except that the Observable object works for asynchronous error handling as well.

Whenever an error is encountered, it races downstream, skipping past operators until it encounters an error handler. In our case, the handleError method re-throws the error, allowing us to intercept it within the service but also to let the subscriber provide its own onError callback that fires even further downstream. Error handling shows us that we haven’t taken full advantage of our stream, even with all the cool stuff we’ve already accomplished. It’s trivial to add a retry operator after our HTTP requests, which retries an individual request if it returns an error. As a preventative measure, we could also add an operator between the rangegenerator and the requests, adding some form of rate-limiting so that we don’t spam the server with too many requests all at once.

Recap: Learning Angular 2 Isn’t Just About a New Framework

Learning Angular 2 is more like meeting an entirely new family, and some of their relationships are complicated. Hopefully I’ve managed to demonstrate that these relationships evolved for a reason, and there’s a lot to be gained by respecting the dynamics that exist within this ecosystem. Hopefully you enjoyed this article as well, because I’ve barely scratched the surface, and there’s a lot more to say on this subject.

This article was written by Ethan James, a Toptal Javascript developer.
I started out wanting to write a step-by-step guide for upgrading an app from Angular 1.5 to Angular before I was politely informed by my editor that she needed an article rather than a novel. After much deliberation, I accepted that I needed to start with a broad survey of the changes in Angular 2, hitting all the points covered in Jason Aden’s Getting Past Hello World in Angular 2 article. …Oops. Go ahead and read it to get an overview of Angular 2’s new features, but for a hands-on approach keep your browser right here.

I want this to become a series that eventually encompasses the entire process of upgrading our demo app to Angular 2. For now, though, let’s start with a single service. Let’s take a meandering walk through the code and I’ll answer any questions you may have, such as….

‘OH NO WHY IS EVERYTHING SO DIFFERENT’

Angular: The Old Way

If you’re like me, the Angular 2 quickstart guide might have been the first time you ever looked at TypeScript. Real quickly, according to its own website, TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. You install the transpiler (similar to Babel or Traceur) and you wind up with a magical language that supports ES2015 & ES2016 language features as well as strong typing.

You may find it reassuring to know that none of this arcane setup is strictly necessary. It’s not terribly difficult to write Angular 2 code in plain old JavaScript, although I don’t think it’s worth it to do so. It’s nice to recognize familiar territory, but so much of what’s new and exciting about Angular 2 is its new way of thinking rather than its new architecture.

Get Your Angular 2 On: Upgrading from 1.5 - Image 1
What’s new and exciting about Angular 2 is its new way of thinking rather than its new architecture.

So let’s look at this service that I upgraded from Angular 1.5 to 2.0.0-beta.17. It’s a fairly standard Angular 1.x service, with just a couple of interesting features that I tried to note in the comments. It’s a bit more complicated than your standard toy application, but all it’s really doing is querying , a freely-available APIthat aggregates listings from rental providers like Airbnb. Sorry, it’s quite a bit of code.

zilyo.service.js (1.5.5)

'use strict'; function zilyoService($http, $filter, $q) { // it's a singleton, so set up some instance and static variables in the same place var baseUrl = "https://zilyo.p.mashape.com/search"; var countUrl = "https://zilyo.p.mashape.com/count"; var state = { callbacks: {}, params: {} }; // interesting function - send the parameters to the server and ask // how many pages of results there will be, then process them in handleCount function get(params, callbacks) { // set up the state object if (params) { state.params = params; } if (callbacks) { state.callbacks = callbacks; } // get a count of the number of pages of search results return $http.get(countUrl + "?" + parameterize(state.params)) .then(extractData, handleError) .then(handleCount); } // make the factory return { get : get }; // boring function - takes an object of URL query params and stringifies them function parameterize(params) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } // interesting function - takes the results of the "count" AJAX call and // spins off a call for each results page - notice the unpleasant imperativeness function handleCount(response) { var pages = response.data.result.totalPages; if (typeof state.callbacks.onCountResults === "function") { state.callbacks.onCountResults(response.data); } // request each page var requests = _.times(pages, function (i) { var params = Object.assign({}, { page : i + 1 }, state.params); return fetch(baseUrl, params); }); // and wrap all requests in a promise return $q.all(requests).then(function (response) { if (typeof state.callbacks.onCompleted === "function") { state.callbacks.onCompleted(response); } return response; }); } // interesting function - fetch an individual page of results // notice how a special callback is required because the $q.all wrapper // will only return once ALL pages have been fetched function fetch(url, params) { return $http.get(url + "?" + parameterize(params)).then(function(response) { if (typeof state.callbacks.onFetchPage == "function") { // emit each page as it arrives state.callbacks.onFetchPage(response.data); } return response.data; // took me 15 minutes to realize I needed this }, (response) => console.log(response)); } // boring function - takes the result object and makes sure it's defined function extractData(res) { return res || { }; } // boring function - log errors, provide teaser for greater ambitions function handleError (error) { // In a real world app, we might send the error to remote logging infrastructure var errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return errMsg; } } // register the service angular.module('angularZilyoApp').factory('zilyoService', zilyoService); The wrinkle in this particular app is that it shows results on a map. Other services handle multiple pages of results by implementing pagination or lazy scrollers, which allows them to retrieve one neat page of results at a time. However, we want to show all results within the search area, and we want them to appear as soon as they return from the server rather than suddenly appearing once all pages are loaded. Additionally, we want to display progress updates to the user so that they have some idea of what’s happening.

In order to accomplish this in Angular 1.5, we resort to callbacks. Promises get us partway there, as you can see from the $q.all wrapper that triggers the onCompleted callback, but things still get pretty messy.

Then we bring in to create all of the page requests for us, and each request is responsible for executing the onFetchPage callback to make sure that it’s added to the map as soon as it’s available. But that gets complicated. As you can see from the comments, I got lost in my own logic and couldn’t get a handle on what was being returned to which promise when.

The overall neatness of the code suffers even more (far more than is strictly necessary), because once I become confused, it only spirals downward from there. Say it with me, please…

‘THERE HAS TO BE A BETTER WAY’

Angular 2: A New Way of Thinking

There is a better way, and I’m going to show it to you. I’m not going to spend too much time on the ES6 (a.k.a. ES2015) because there are far better places to learn about that stuff, and if you need a jumping-off point, ES6-Features.org has a good overview of all of the fun new features. Consider this updated AngularJS 2 code.





.service.ts (2.0.0-beta.17)

import {Injectable} from 'angular2/core'; import {Http, Response, Headers, RequestOptions} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; @Injectable() export class ZilyoService { constructor(private http: Http) {} private _searchUrl = "https://zilyo.p.mashape.com/search"; private _countUrl = "https://zilyo.p.mashape.com/count"; private parameterize(params: {}) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } get(params: {}, onCountResults) { return this.http.get(this._countUrl, { search: this.parameterize(params) }) .map(this.extractData) .map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; }) .flatMap(results => Observable.range(1, results.totalPages)) .flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); }) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { if (res.status 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); } let body = res.json(); return body.result || { }; } private handleError (error: any) { // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return Observable.throw(errMsg); } } Cool! Let’s walk through this line by line. Again, the TypeScript transpiler lets us use any ES6 features that we want because it converts everything to vanilla JavaScript.

The import statements at the beginning are simply using ES6 to load the modules that we need. Since I do most of my development in ES5 (aka regular JavaScript), I must admit that it’s a bit annoying to suddenly need to start listing every object that I plan to use.

However, keep in mind that TypeScript is everything down to JavaScript and is secretly usingSystemJS to handle module loading. The dependencies are all being loaded in asynchronously, and it is (allegedly) able to bundle your code in a way that strips out symbols that you haven’t imported. Plus it all supports aggressive minification, which sounds very painful. Those import statements are a small price to pay to avoid dealing with all of that noise.

Get Your Angular 2 On: Upgrading from 1.5 - Image 5
Import statements are a small price to pay for what's going on behind the scenes.

Anyway, apart from loading in selective features from Angular 2 itself, take special notice of the line import {Observable} from 'rxjs/Observable';. RxJS is a mind-bending, crazy-cool reactive programming library that provides some of the infrastructure underlying Angular 2. We will definitely be hearing from it later.

Now we come to @Injectable().

I’m still not totally sure what that to be honest, but the beauty of declarative programming is that we don’t always need to understand the details. It’s called a decorator, which is a fancy TypeScript construct capable of applying properties to the class (or ) that follows it. In this case, @Injectable() teaches our service how to be injected into a component. The best demonstration comes straight from the horse’s mouth, but it’s quite long so here’s a sneak peek of how it looks in our AppComponent:

@Component({ ... providers: [HTTP_PROVIDERS, ..., ZilyoService] }) Next up is the class definition itself. It has export statement before it, which means, you guessed it, we can import our service into another file. In practice, we’ll be importing our service into our AppComponentcomponent, as above.

Get Your Angular 2 On: Upgrading from 1.5 - Image 6
@Injectable() teaches our service how to be injected into a component.

Right after that is the constructor, where you can see some real dependency injection in action. The line constructor(private http:Http) {} adds a private instance variable named http that TypeScript magically recognizes as an instance of the Http service. Point goes to TypeScript!

After that, it’s just some regular-looking instance variables and a utility function before we get to the real meat and potatoes, the get function. Here we see Http in action. It looks a lot like Angular 1’s promise-based approach, but under the it’s way cooler. Being built on RxJS means that we get a couple of big advantages over promises:

  • We can cancel the Observable if we no longer care about the response. This might be the case if we’re building a typeahead autocomplete field, and no longer care about the results for ca once they’ve entered cat.
  • The Observable can emit multiple values and the subscriber will be called over and over to consume them as they are produced.
The first one is great in a lot of circumstances, but it’s the second that we’re focusing on in our new service. Let’s go through the get function line by line:

return this.http.get(this._countUrl, { search: this.parameterize(params) }) It looks pretty similar to the promise-based HTTP call you would see in Angular 1. In this case, we’re sending the query parameters to get a count of all matching results.

.map(this.extractData) Once the AJAX call returns, it will send the response down the stream. The method map is conceptually similar to an array’s map function, but it also behaves like a promise’s then method because it waits for whatever was happening upstream to complete, regardless of synchronicity or asynchronicity. In this case, it simply accepts the response object and teases out the JSON data to pass downstream. Now we have:

.map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; }) We still have one awkward callback that we need to slide in there. See, it’s not all magic, but we can process onCountResults as soon the AJAX call returns, all without leaving our stream. That’s not too bad. As for the next line:

.flatMap(results => Observable.range(1, results.totalPages))

Uh oh, can you feel it? A subtle hush has come over the onlooking crowd, and you can tell that something major is about to happen. What does this line even mean? The right-hand part isn’t that crazy. It creates range, which I think of as a glorified Observable-wrapped array. If results.totalPages equals 5, you end up with something like Observable.of([1,2,3,4,5]).

flatMap is, wait for it, a combination of flatten and map. There’s a great video explaining the concept at Egghead.io, but my strategy is to think of every Observable as an array. Observable.range creates its own wrapper, leaving us with the 2-dimensional array [[1,2,3,4,5]]. flatMap flattens the outer array, leaving us with [1,2,3,4,5], then map simply maps over the array, passing the values downstream one at a time. So this line accepts an integer (totalPages) and converts it into a stream of integers from 1 to totalPages. It may not seem like much, but that’s all we need to set up.

THE PRESTIGE

I really wanted to get this on one line to increase its impact, but I guess you can’t win them all. Here we see what happens to the stream of integers that we set up on the last line. They flow into this step one by one, then are added to the query as a page parameter before finally being packaged into a brand new AJAX request and sent off to fetch a page of results. Here’s that code:

.flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); }) If totalPages was 5, we construct 5 GET requests and send them all off simultaneously. flatMap subscribes to each new Observable, so when the requests return (in any order) they are unwrapped and each response (like a page of results) is pushed downstream one at a time.

Let’s look at how this whole thing works from another angle. From our originating count request, we find the total number of pages of results. We create a new AJAX request for each page, and no matter when they return (or in what order), they are pushed out into the stream as soon as they are ready. All that our component needs to do is subscribe to the Observable returned by our get method, and it will receive each page, one after the other, all from a single stream. Take that, promises.

Get Your Angular 2 On: Upgrading from 1.5 - Image 7
The component will receive each page, one after the other, all from a single stream.

It’s all a bit anti-climactic after that:

.map(this.extractData).catch(this.handleError); As each response object arrives from the flatMap, its JSON is extracted in the same manner as the response from the count request. Tacked on to the end there is the catch operator, which helps illustrate how stream-based RxJS error handling works. It’s pretty similar to the traditional try/catch paradigm, except that the Observable object works for asynchronous error handling as well.

Whenever an error is encountered, it races downstream, skipping past operators until it encounters an error handler. In our case, the handleError method re-throws the error, allowing us to intercept it within the service but also to let the subscriber provide its own onError callback that fires even further downstream. Error handling shows us that we haven’t taken full advantage of our stream, even with all the cool stuff we’ve already accomplished. It’s trivial to add retry operator after our HTTP requests, which retries an individual request if it returns an error. As a preventative measure, we could also add an operator between the rangegenerator and the requests, adding some form of rate-limiting so that we don’t spam the server with too many requests all at once.

Recap: Learning Angular 2 Isn’t Just About a New Framework

Learning Angular 2 is more like meeting an entirely new family, and some of their relationships are complicated. I’ve managed to demonstrate that these relationships evolved for a reason, and there’s a lot to be gained by respecting the dynamics that exist within this ecosystem. you enjoyed this article as well, because I’ve barely scratched the surface, and there’s a lot more to say on this subject.

This article was written by Ethan James, a Toptal Javascript developer.



This blog is listed under Open Source and Development & Implementations Community

Related Posts:
Post a Comment

Please notify me the replies via email.

Important:
  • We hope the conversations that take place on MyTechLogy.com will be constructive and thought-provoking.
  • To ensure the quality of the discussion, our moderators may review/edit the comments for clarity and relevance.
  • Comments that are promotional, mean-spirited, or off-topic may be deleted per the moderators' judgment.
You may also be interested in
 
Awards & Accolades for MyTechLogy
Winner of
REDHERRING
Top 100 Asia
Finalist at SiTF Awards 2014 under the category Best Social & Community Product
Finalist at HR Vendor of the Year 2015 Awards under the category Best Learning Management System
Finalist at HR Vendor of the Year 2015 Awards under the category Best Talent Management Software
Hidden Image Url

Back to Top