Angular 5 with Angular CLI

Angular 5 Overview

Angular 5的快速开发,测试和部署可以使用Angular CLI工具完成。Angular 5是基于Typescript语言开发的Web前端框架。

Angular 5 quick start

Angular 5 basic prject development tutorial

Application shell

Angular 5应用的基本框架,可以用ng new {projectname}命令生成,一个简单的Angular项目需要包含一个app module和app component。在app component中,会定义一个控件,作为整个Angular app的入口,一般写在index.html中。

Angular Component

在已经有的app component基础上,可以用ng generate component {dir/componetname}命令生成更多的组件,新的组件组成元素和app component一样,也是html模板,ts组件功能定义,和css组件风格。一般意义上,在ts中定义控件directive的名称,在html模板中,可以直接调用该directive。

每个已经创建好的component会被自动import进入app.module.ts文件,从而在Angular应用启动时,能自动寻找到对应的component并加载。如果developer需要在自己定义的component中引用其他component/service组件,也需要定义相似的import语句,否则Angualr引擎并不能成功识别调用组件。

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
//Angular components
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

//Angular application shell
import { AppComponent } from './app.component';
//Additional costomized components
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
//Additional service modules
import { HeroService } from './hero.service';

@NgModule({
declarations: [
AppComponent,
HeroesComponent,
HeroDetailComponent,
],
imports: [
BrowserModule,
FormsModule
],
providers: [
HeroService
],
bootstrap: [ AppComponent ]
})

Angular Service

Angular提供了service module来支持现在Web前端数据获取和更新功能。可以用ng generate service {dir/servicename} –module=app命令来生成。

Angular Routing

Angular提供了routing来允许Web前端以single page application(SPA)方式渲染多个url的页面。可以用ng generate module app-routing –flat –module=app生成app.routing.ts模块。app routing模块隐式定义了控件,这是一个可以根据输入url进行跳转的控件。一般放在app componet html模板中,作为Angular应用的跳转渲染单元。这个控件本身,并不能提供跳转入口,一般需要写锚,在html模板显式来定义url。

1
2
3
4
5
<nav>
<a routerLink="/heroes">Heroes</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>

此外,app routing模块本身,定义了url跳转的逻辑。由于Angualar的html模块本身具有嵌套性,因而在routing逻辑中,只要引入自定义的directive控件,就能自动渲染出控件中所嵌套的所有元素。可以说,Angular的程序设计思想,就是基于模板设计的,每个模板都是一个自定义的DOM元素,允许在Angular的控制域中任意的复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { NgModule }             from '@angular/core';
// Angular routing module
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';

const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];

@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}

Angular HTTP

Angular提供了HttpClient库作为Restful API的utility来完成Web前端的服务器数据交互。在程序中,HttpClient库一般本身不会单独存在于componnet中,而是作为Angular Service模块的底层调用库。

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Service module
import { Injectable } from '@angular/core';
// import httpclient Angular module
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';

import { Hero } from './hero';
import { MessageService } from './message.service';

const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable()
export class HeroService {

private heroesUrl = 'api/heroes'; // URL to web api

constructor(
private http: HttpClient,
private messageService: MessageService) { }

/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(heroes => this.log(`fetched heroes`)),
catchError(this.handleError('getHeroes', []))
);
}

/** GET hero by id. Return `undefined` when id not found */
getHeroNo404<Data>(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/?id=${id}`;
return this.http.get<Hero[]>(url)
.pipe(
map(heroes => heroes[0]), // returns a {0|1} element array
tap(h => {
const outcome = h ? `fetched` : `did not find`;
this.log(`${outcome} hero id=${id}`);
}),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}

/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}

/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
if (!term.trim()) {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}

//////// Save methods //////////

/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}

/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;

return this.http.delete<Hero>(url, httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero'))
);
}

/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero'))
);
}

/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {

// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead

// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);

// Let the app keep running by returning an empty result.
return of(result as T);
};
}

/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add('HeroService: ' + message);
}
}

HttpClient除了和真正的服务器交互外,还可以和in-memory的数据服务器进行虚拟交互,也就是说,在不修改(略微)原有程序代码的情况下,可以自己运用一个npm package设立一个in-memory的数据服务器,HttpClient并不知道request已经被这个in-memory服务器拦截并返回内存中的数据。

在前后端分离的开发过程中,如果要引用此功能,需要预先安装angular-in-memory-web-api的npm包。

1
$ npm install angular-in-memory-web-api@0.5 --save

然后,创建in-memory-data.service模块,存储内存中的数据,作为HttpClient访问交互的相关数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { InMemoryDbService } from 'angular-in-memory-web-api';

export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroes = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
return {heroes};
}
}

最后,在app module模块中引入对in-memory-web-api的引用,和in-memory-data service模块的使用,并配置好in-memeory-server对应的data/service来源。

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
import { NgModule }       from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

// import in-memroy-data servier related module
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroSearchComponent } from './hero-search/hero-search.component';
import { HeroService } from './hero.service';
import { MessageService } from './message.service';
import { MessagesComponent } from './messages/messages.component';

@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,

// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)
],
declarations: [
AppComponent,
DashboardComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent,
HeroSearchComponent
],
providers: [ HeroService, MessageService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

Angular Data Model – Class

In Angular 5, data model is encapsulated through classes, mainly for rendering templates in components. Creating a new class by Angular Cli:

1
$ ng generate class {classname}