Angular 2 package

Angular 2 uses systemjs as the module loader to load user’s modules as well as the dependent libraries. It does not import all the dependencies into memory at the beginning but only loads one when it is needed. Whenever systemjs loads a module, it uses its config file to locate the path of the module and then initiate a XHR request to load it. The dynamic loading mechanism is very beneficial for developers because it allows them to only load the modified code. But it brings two problems in the production environment.

  • The XHR requests create extra network traffic which may slow down the entire application especially for those who only have limited bandwidth.
  • An Angular 2 application usually defines dozens of third party dependencies which internally may rely on other libraries. It is very difficult to make a proper and clean deployment.

To solve the problems, we would like to package the application along with all the dependencies to a single file and load it once in the index.html.

Preparation

1
npm install systemjs-builder --save-dev
  • Install gulp

    1
    2
    npm install --global gulp-cli
    npm install gulp --save-dev
  • Create a task in gulpfile.js as the following
    Assume all the application codes are under “app” folder and the main entry file is main.js. The file systemjs.config.js is the config file for systemjs.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var gulp = require('gulp');
    var Builder = require('systemjs-builder');
    gulp.task('app', function () {
    var builder = new Builder('.', './systemjs.config.js');
    builder.bundle('app/main.js', './dist/main-min.js', {minify: true, mangle: true})
    .then(function () {
    console.log('Build completed');
    })
    .catch(function (err) {
    console.log('Build failed');
    console.log(err);
    });
    });
  • And run the task

    1
    gulp app

It eventually generates a minified file main-min.js under the dist folder, we just inculde it in the index.html and it is good to go.

1
<script src="dist/main-min.js"></script>

Sometimes, in order to speed up the build process, we want to create one package for the barely changed dependencies and another package for the frequently modified applications code respectively. So we need to split the above task into two tasks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//build main-dep-min.js that only contains the dependencies of the main application
gulp.task('app-dep-build', function () {
var builder = new Builder('.', './systemjs.config.js');
builder.bundle('app/**/*.js - [app/**/*]', './dist/main-dep-min.js', {minify: true, mangle: true})
.then(function () {
console.log('Build dep complete');
})
.catch(function (err) {
console.log('Build error');
console.log(err);
});
});
//build a main-app-min.js that contains the main application without the dependencies
gulp.task('app-build', function () {
var builder = new Builder('.', './systemjs.config.js');
builder.bundle('[app/**/*.js]', './dist/main-app-min.js', {minify: true, mangle: true})
.then(function () {
console.log('Build app complete');
})
.catch(function (err) {
console.log('Build error');
console.log(err);
});
});

Angular 2 http

Preparation

Angular 2 comes with a built-in http client to easily make RESTful calls. The http client is provided in the HttpModule. So we first need to load it in the module file.

1
2
3
4
5
6
7
8
9
import {HttpModule} from '@angular/http';
@NgModule({
imports: [HttpModule...],
declarations: [...],
providers: [...],
bootstrap: [AppComponent]
})
export class AppModule {
}

Then in the service class we can ask the Angular to inject the Http service to us.

1
2
3
4
5
6
import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
@Injectable()
export class MyService {
constructor(private http: Http){}
}

Retrieve data

Now we are ready to use the http instance to access the backend service. It is simple to use http GET to access a backend resource.

1
2
let url = 'serviceUrl';
this.http.get(url);

The only required argument is the url, e.g ‘/employee/123’. All http calls in Angular 2 is asynchronous. It returns Observable<Response> which needs to be subscribed to retrieve the returned value.

1
this.http.get(url).subscribe(resp => console.log(resp));

We most of the time also need to extract the real meaningful value or check the http status from the http Response.

  • To check the http status.

    1
    let status = resp.status;
  • To get the text value of from the response body

    1
    let txt = resp.text();
  • To convert the response body to a json

    1
    let value = resp.json() as <YourType>;

So if we have a service class called EmployeeService which can retrieve an employee, we can use http client like this:

1
2
3
4
5
6
7
8
this.http.get(svcUrl)
.map(resp => resp.json() as Employee)
.catch(error => {
if (error.status === 401) {
this.router.navigate(['/login']);
}
})
.subscribe(employee => console.log('Employee is ' + employee));

Submit data

We use operation POST to submit a request to the backend. It requires not only a URL string but also a payload object and an optional header to indicate the content type.

Text payload

The text payload can be either be a json string if the ‘Content-Type’ is set to ‘application/json’ in the header or a plain string like ‘p1=v1&p2=v2&p3=v3’ if the ‘Content-Type’ is set to ‘application/x-www-form-urlencoded’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {Headers, RequestOptions} from "@angular/http";
let isJson;
......
let svcUrl = '/user';
let parameters = isJson?
JSON.stringify({name: 'John Doe', age: 30}) :
'name=' + encodeURIComponent('John Doe') + "&age=30";
this.http.post(svcUrl, parameters, this.getHeader(isJson));
getHeader(isJson: boolean): RequestOptions {
let headers = new Headers();
if (!isJson) {
headers.append('Content-Type', 'application/x-www-form-urlencoded');
} else {
headers.append('Content-Type', 'application/json');
}
return new RequestOptions({ headers: headers });
}

URLSearchParams payload

If we want to submit an raw javascript object, we have to serialize it to a string. Angular 2 provides a URLSearchParams class to simply this process.

1
2
3
4
5
6
let form = {name: 'lli', age: 10};
let input = new URLSearchParams();
for (let p in form) {
input.set(p, form[p]);
}
this.http.post(svcUrl, input).subscribe(resp => console.log(resp));

When Angular 2 detects that the payload is an URLSearchParams object, it will call its toString method and automatically set the ‘Content-Type’ to ‘application/x-www-form-urlencoded’.

Object payload

There is a another short path to submit a json string payload by just simply passing the raw javascript object to the POST method. Under the hood, Angular 2 will automatically set the ‘Content-Type’ to ‘application/json’ and apply JSON.stringify on the object.

1
2
3
4
5
6
this.http.post(svcUrl, {name: 'John Doe', age: 30}).subscribe(resp => console.log(resp));
equals to
this.http.post(svcUrl, JSON.stringify({name: 'John Doe', age: 30}), this.getHeader(true))
.subscribe(resp => console.log(resp));

Angular 2 animation

The animation of Angular 2 is built on top of its component and is meta driven. In another word, instead of using certain API to implement the animation in the code, Angular 2 requires you to declare the animation in the component’s meta data.

Here is a simple example

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
import {style, animate, transition, state, trigger} from '@angular/core';
@component({
selector: 'mypane',
template: '<div [@showState]="show">This is a test view</div>',
animations: [
trigger('showState', [
state('on', style({opacity: 1})),
state('off', style({opacity: 0})),
transition('on => off', [animate('0.2s ease-out')]),
transition('off => on', [
style({transform: scale(0.5)}),
animate('0.2s ease-in', style({transform:scale(1)}))
])
])
]
})
export class MyPane {
show: string;
turnOn() {
this.show = "on";
}
turnOff() {
this.show = "off";
}
}

Let’s discuss this sample step by step

  1. We need to define an animation entry.
    An animation entry or animation trigger must be defined for an element to indicate which element should have the animation effect. Here the ‘@showState’ is defined for the ‘div’.

  2. We create a ‘animations’ entry in the component meta data.
    The ‘animations’ contains a series of ‘triggers’, e.g trigger(‘showState’) in the above example, each of which corresponds to an animation entry. Please be noted, we need to remove the leading ‘@’ from the entry name when put it in the ‘trigger’.

  3. Each animation entry can have multiple states and optionally we can define css style for each state.
    Here we define styles for state ‘on’ and ‘off’

    1
    2
    state('on', style({opacity: 1})),
    state('off', style({opacity: 0})),
  4. We need to define the animation effect when an animation entry changes from one state to another. The syntax is transition(‘state change expression’, [step1, step2,…,stepN]).
    The ‘state change expression’ describes how the states change, for example ‘on => off’ means changing state from ‘on’ to ‘off’, ‘off => on’ means changing state from ‘off’ to ‘on’ and ‘on <=> off’ means a bidirectional change.
    The [steps] is the most important part for the animation implementation. It lists all the actions the animation effect will involve. The legitimate actions are ‘style’ and ‘animate’. The ‘style’ action can only be used as the first one, indicating the initial style of the animation. We will focus on the ‘animate’ action. The ‘animate’ accepts two arguments, the first of which is a string with format ‘duration delay ease-function’. The duration and delay both use time unit like ‘0.5s’ or ‘200ms’. The ease function tells how the animation frames change along the time line. The most popular ones are ‘ease-in’ and ‘ease-out’ and you can find more from here. The second argument is an optional ‘style’ to tell what is the final style when the animation ends. If it is missing, the style defined by the ‘state’ will be used.
    Please be noted: All the styles defined in the ‘transition’ are not permanent and will disappear after the animation complete.

Let’s go through the above example to see how the ‘transition’ works.

1
2
3
state('on', style({opacity: 1})),
state('off', style({opacity: 0})),
transition('on => off', [animate('0.2s ease-out')]),

The code above means when the state changes from ‘on’ to ‘off’, an animation will start without delay and last for 0.2 second, playing with ‘ease-out’ function. Because we defined the style for state ‘on’ as ‘opacity: 1’ and style for state ‘off’ as ‘opacity: 0’. The animation implements a fade-out effect.

1
2
3
4
transition('off => on', [
style({transform: scale(0.5)}),
animate('0.2s ease-in', style({transform:scale(1)}))
])

The code above means when the state changes from ‘off’ to ‘on’, an animation will start without delay and last for 0.2 second, playing with ‘ease-in’ function. In addition, we specify the scale factor 0.5 at the beginning and scale factor 1 at the end. The animation implements a fade-in and scaling effect and the ‘scale’ style will be removed after the animation is finished.

Sometimes we want to have some animation effects when navigating pages by the router. When navigating from page A to page B, the page A is destroyed and the page B is initiated. The trick is how to define the animation entry and states changes. Angular 2 provides two special states, “*“ and “void”. “*“ stands for any state and “void” stands for none state. Therefore, the destroying process of page A can be described as “* => void” and the initiating process of page B can be described as “void => *“.

Additionally, we also don’t need to bind the animation entry with a variable but just assign it with any arbitrary value.

1
<div [@showState]="true">This is a test view</div>

Or even simpler, we can just define the animation entry in the meta but not the template view.

1
2
3
4
5
6
@component({
selector: 'mypane',
template: '<div>This is a test view</div>',
host: { '[@showState]': 'true' },
animations: [...]
})

We define the animation entry in the ‘host’ which is the wrapper parent element automatically created by the Angular 2.