Upgrade from Angular 1.5 to Angular 2: Get Started Now

I began with the intention of crafting a detailed walkthrough for migrating an application from Angular 1.5 to Angular 2. However, my editor wisely advised that a concise article, rather than an extensive manual, would be more suitable. After careful consideration, I agreed to start by presenting a comprehensive overview of the advancements introduced in Angular 2, drawing upon the key points outlined in Jason Aden’s Getting Past Hello World in Angular 2 article. …Oops. While that resource provides a great general understanding of Angular 2’s new aspects, for a practical, hands-on exploration, please remain on this page.

My goal is to develop this into a series that will ultimately encompass the complete transition of our demo application to Angular 2. But for the time being, we’ll focus on a single service. Let’s embark on a detailed examination of the code, during which I’ll address any questions you might have, such as….

‘OH NO WHY IS EVERYTHING SO DIFFERENT’

Angular: The Conventional Approach

If your experience mirrors mine, the Angular 2 quickstart guide might have marked your initial encounter with TypeScript. In a nutshell, as per its own website, TypeScript is “a typed superset of JavaScript that compiles to plain JavaScript”. Upon installing the transpiler (akin to Babel or Traceur), you gain access to a powerful language that supports ES2015 & ES2016 language features alongside strong typing.

Rest assured that this seemingly intricate setup is not mandatory. It’s not overly difficult to write Angular 2 code in plain old JavaScript, though I wouldn’t recommend it. While it’s comforting to recognize familiar elements, the truly captivating aspects of Angular 2 lie in its paradigm shift rather than its architectural changes.

This post walks through upgrading a service to Angular 2 from 1.5.
What’s new and exciting about Angular 2 is its new way of thinking rather than its new architecture.

Now, let’s analyze the service that I migrated from Angular 1.5 to 2.0.0-beta.17. It’s a fairly typical Angular 1.x service, with a few notable characteristics that I’ve highlighted in the comments. Although slightly more involved than your average rudimentary application, its primary function is querying Zilyo, a freely-available API that gathers listings from rental platforms such as Airbnb. Please bear with the extensive code snippet.

zilyo.service.js (1.5.5)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
'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 unique challenge in this application stems from displaying results on a map. Other services handle multiple result pages using pagination or lazy loading, retrieving one page at a time. However, we aim to showcase all results within the search area instantaneously as they arrive from the server, rather than abruptly revealing them after all pages load. Moreover, we want to provide users with progress updates to keep them informed.

To achieve this in Angular 1.5, we rely on callbacks. Promises partially address this, as evident in the $q.all wrapper triggering the onCompleted callback. However, things still become somewhat convoluted.

Introducing lodash helps generate all the page requests, each responsible for executing the onFetchPage callback to ensure immediate map addition upon availability. However, this introduces its own complexities. As the comments reveal, I became entangled in my own logic, struggling to track the data flow between promises.

The code’s overall elegance suffers further (exceeding necessity) because confusion only exacerbates the issue. Let’s collectively acknowledge the truth…

‘THERE HAS TO BE A BETTER WAY’

Angular 2: A Fresh Perspective

Fortunately, there’s a superior approach, which I’ll gladly demonstrate. I won’t delve deeply into ES6 (also known as ES2015) concepts as numerous excellent resources cover them extensively. If you seek a starting point, ES6-Features.org offers a solid overview of the exciting new features. Consider the following updated AngularJS 2 code:

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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);
  }
}

Impressive! Let’s break it down step by step. The TypeScript transpiler grants us the freedom to utilize ES6 features since it translates everything into standard JavaScript.

The initial import statements leverage ES6 for loading the necessary modules. Accustomed to ES5 (regular JavaScript) development, I find it slightly inconvenient to explicitly declare every object I intend to use.

However, remember that TypeScript ultimately transpiles everything into JavaScript, discreetly employing SystemJS for module loading. Dependencies load asynchronously, and it purportedly bundles code intelligently, omitting unused symbols. Furthermore, it boasts support for “aggressive minification,” a rather daunting concept. Those import statements are a small price to pay for a cleaner, more manageable codebase.

Import statements in Angular do a lot behind the scenes.
Import statements are a small price to pay for what's going on behind the scenes.

Besides selectively importing features from Angular 2, pay close attention to import {Observable} from 'rxjs/Observable';. RxJS is an impressive, mind-boggling reactive programming library that forms part of Angular 2’s foundation. We’ll definitely revisit it later.

Next comes @Injectable().

To be frank, its exact function remains somewhat unclear to me. However, the beauty of declarative programming lies in not needing to grasp every intricate detail. It’s called a decorator, an elegant TypeScript construct that applies properties to the subsequent class (or other object). In this instance, @Injectable() equips our service with the knowledge of how to be injected into a component. While the official documentation provides the most accurate explanation, it tends to be lengthy. So here’s a glimpse of its implementation in our AppComponent:

1
2
3
4
@Component({
  ...
  providers: [HTTP_PROVIDERS, ..., ZilyoService]
})

Moving on to the class definition itself, we see an export statement, implying, as you might have guessed, that we can import our service into another file. Practically speaking, we’ll import it into our AppComponent component, as shown earlier.

@Injectable() teaches our service how to be injected into a component.
@Injectable() teaches our service how to be injected into a component.

Following the class definition is the constructor, where we witness dependency injection in action. The line constructor(private http:Http) {} introduces a private instance variable named http, which TypeScript cleverly recognizes as an instance of the Http service. Kudos to TypeScript!

Beyond this, we have some standard instance variables and a utility function before reaching the heart of the matter, the get function. This is where we see Http in action. Although it resembles Angular 1’s promise-based approach, its underlying mechanism is far more sophisticated. Built on RxJS, it offers two significant advantages over promises:

  • The ability to cancel the Observable if the response is no longer relevant. This is useful in scenarios like typeahead autocomplete fields, where results for “ca” become irrelevant once the user types “cat.”
  • The ability of the Observable to emit multiple values, invoking the subscriber repeatedly to consume them as they are produced.

While the first advantage is valuable in various situations, our focus lies on the second one within our new service. Let’s dissect the get function line by line:

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

This appears similar to the promise-based HTTP call in Angular 1. Here, we send the query parameters to retrieve a count of all matching results.

1
.map(this.extractData)

Upon completion of the AJAX call, the response is sent downstream. The map method, conceptually similar to an array’s map function, also behaves like a promise’s then method by waiting for the upstream operation to finish, regardless of its synchronous or asynchronous nature. In this case, it simply extracts the JSON data from the response object and passes it downstream. Now we have:

1
2
3
4
5
6
.map(results => {
  if (typeof onCountResults === "function") {
    onCountResults(results.totalResults);
  }
  return results;
})

We still have one lingering callback, onCountResults, which we need to incorporate. It’s not pure magic, but we can process it immediately upon the AJAX call’s return, all without disrupting our stream. Not too shabby. Moving onto the next line:

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

Can you sense it? A palpable tension has gripped the audience as they anticipate something significant. What does this line even signify? The right-hand side isn’t particularly complex. It generates an RxJS range, essentially an Observable-wrapped array. If results.totalPages equals 5, the result resembles Observable.of([1,2,3,4,5]).

flatMap is, as the name suggests, a fusion of flatten and map. A helpful video illustrating this concept is available at Egghead.io. However, my approach involves visualizing each Observable as an array. Observable.range creates its own wrapper, resulting in a 2-dimensional array [[1,2,3,4,5]]. flatMap flattens this outer array into [1,2,3,4,5], followed by map iterating over the array and passing the values downstream one by one. In essence, this line takes an integer (totalPages) and transforms it into a stream of integers from 1 to totalPages. While seemingly insignificant, this sets the stage for the next step.

THE PRESTIGE

Ideally, this would be on a single line for greater impact, but some battles are lost. This line showcases the fate of the integer stream generated in the previous step. The integers flow in sequentially, get incorporated into the query as page parameters, and are then packaged into new AJAX requests dispatched to fetch individual result pages. Here’s the code:

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

If totalPages was 5, we construct and send 5 concurrent GET requests. flatMap subscribes to each new Observable. Consequently, as the requests return (in any order), they are unwrapped, and each response (representing a page of results) is pushed downstream individually.

Let’s shift perspectives and analyze the process as a whole. Starting with the initial “count” request, we determine the total pages of results. For each page, we create a new AJAX request. Regardless of their return order or timing, these requests are pushed into the stream as soon as they are ready. Our component merely needs to subscribe to the Observable returned by our get method to receive each page sequentially from a single stream. Take that, promises.

Each response is pushed downstream one at a time.
The component will receive each page, one after the other, all from a single stream.

The remaining code feels somewhat anticlimactic after this:

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

As each response object arrives from flatMap, we extract its JSON, mirroring the process for the count request response. Appended to the end is the catch operator, illustrating the mechanics of stream-based RxJS error handling. It’s reminiscent of the traditional try/catch paradigm but adapted for asynchronous error handling within the Observable object.

Upon encountering an error, it races downstream, bypassing operators until reaching an error handler. In this case, the handleError method re-throws the error, enabling interception within the service while allowing the subscriber to provide its own onError callback, extending the error handling downstream. This highlights that we haven’t fully exploited our stream’s potential despite our achievements. Adding a retry operator after our HTTP requests, retrying failed individual requests, is trivial. As a precaution, we could insert an operator between the range generator and the requests to implement rate-limiting, preventing server overload from excessive requests.

Recap: Learning Angular 2 Is More Than Just a New Framework

Learning Angular 2 is akin to encountering an entirely new family with complex relationships. Hopefully, I’ve demonstrated that these relationships serve a purpose and respecting the dynamics within this ecosystem yields substantial benefits. I trust you found this article insightful as I’ve barely scratched the surface of this vast topic, leaving much more to explore.

Licensed under CC BY-NC-SA 4.0