Angular基础

第一章:安装配置

设置开发环境

node.js和npm

全局安装Angular CLI

1
npm install -g @angular/cli

创建项目

1
ng new my-app

启动项目

1
2
cd my-app
ng serve --open

ng serve命令会启动开发服务器,监听文件变化,并在修改这些文件时重新构建此应用。

使用--open(或-o)参数可以自动打开浏览器并访问http://localhost:4200/

注:可以设置为cnpm

1
ng set --global packageManager=cnpm

第二章:常用指令

2.1 属性绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<img [src]="heroImageUrl">
<img bind-src="heroImageUrl">
<img src="{{heroImageUrl}}">

heroImageUrl = '../assets/image/img1.png';


<button [disabled]="isUnchanged">Cancel is disabled</button>
<input type="button" value="按钮" [disabled]="true">

isUnchanged = true;


<p><span>{{title}}</span></p>
<p>"<span [innerHTML]="title"></span></p>

title = '标题'

2.2 类名绑定

添加或删除单个

1
2
3
4
// 设置单个class名,通过isSpecial判断是否添加special这个类名
<div [class.special]="isSpecial">The class binding is special</div>

<!--isSpecial = true-->

当想要同时添加或移除多个 CSS 类时,NgClass指令可能是更好的选择。

试试把ngClass绑定到一个 key:value 形式的控制对象。这个对象中的每个 key 都是一个 CSS 类名,如果它的 value 是true,这个类就会被加上,否则就会被移除。

组件方法setCurrentClasses可以把组件的属性currentClasses设置为一个对象,它将会根据三个其它组件的状态为truefalse而添加或移除三个类。

1
2
3
4
5
6
7
8
9
10
11
12
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>


currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}

2.3 style绑定

设置单一样式:

1
2
3
4
5
6
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>

<button [style.margin-left.px]="isSpecial ? 10 : 20" >Big</button>
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>

设置多个内联样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>


currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}

2.4 事件绑定

目标事件
1
2
3
<button (click)="onSave()">Save</button>

<button on-click="onSave()">On Save</button>
$event 和事件处理语句

通过$event获取事件对象

1
2
3
4
5
6
7
<input type="text" [value]="inputVal"
(input) = "inputVal=$event.target.value">`

<!-- 和双项绑定一样 -->
<input type="text" [(ngModel)]="inputVal">

<p>{{inputVal}}</p>

1. 双项绑定

[(ngModel)]=""

虽然NgModel是一个有效的Angular指令,但它默认情况下却是不可用的。 它属于一个可选模块FormsModule。 我们必须选择使用那个模块。

导入 FormsModule

打开app.module.ts文件,并且从@angular/forms库中导入符号FormsModule。 然后把FormsModule添加到@NgModule元数据的imports数组中,它是当前应用正在使用的外部模块列表。

修改后的AppModule是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // NgModel lives here

import { AppComponent } from './app.component';

@NgModule({
imports: [
BrowserModule,
FormsModule // import the FormsModule before binding with [(ngModel)]
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }

使用:

1
<input type="text" [(ngModel)]="hero.name" placeholder="name">

2.遍历

1
<li *ngFor="let hero of heroes">

第三章:父子组件

父组件app.component.ts

子组件hero-detail.components.ts

在创建子组件之前,先在src下创建一个hero.ts,其中存放着一个hero类,这个类会被多个组件引用

1
2
3
4
export class Hero {
id: number;
name: string;
}

之后,在其他的组件中要引用这个公共的类,只需要使用

1
import { Hero } from './hero';	// 引入Hero

引入即可。

3.1 创建子组件

每一个组件,都必须引入Component来表明它是一个组件

在src下创建文件夹hero-detail

并在其中依次创建hero-detail/component.ts/component.html/component.css三个文件

hero-detail.component.ts

1
2
3
4
5
6
7
8
9
10
import { Component, Input } from '@angular/core';	// 引入Componens 和 Input
import { Hero } from '../hero'; // 引入Hero
@Component({
selector: 'hero-detail', // 确定子组件引用时的标签名
templateUrl: './hero-detail.component.html', // 子组件的HTML代码
styleUrls: ['./hero-detail.component.css'] // 子组件的样式
})
export class HeroDetailComponent { // 导出子组件
@Input() hero: Hero; // 1.通过在hero属性前面加上@Input装饰器,来表明它是一个输入属性
}

hero-detail.component.html

1
2
3
4
5
6
7
8
9
<div *ngIf="hero">	// 2.引用父组件传递进来的属性
<h2>{{hero.name}} details!</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name" />
</div>
</div>

3.2 在父组件中引用子组件

1.在AppModule中声明HeroDetailComponent

每个组件都必须在一个(且只有一个)Angular模块中声明。

打开app.module.ts并且导入HeroDetailComponent,以便我们可以引用它。

1
2
3
4
5
6
7
8
9
import { HeroDetailComponent } from './hero-detail/hero-detail.component'; // 引入
@NgModule({
declarations: [
AppComponent,
HeroDetailComponent // 引入
],

})
export class AppModule { }

2.在父组件html中引用

1
2
3
4
5
6
7
8
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail> // 通过[]向子组件传递值,子组件那边用@Input接收

3.3 @ViewChild

有些时候,在父组件中你可能需要用到子组件中的属性或者方法,此时你可以使用@ViewChild

child.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
@Component({
selector: 'app-child',
template: '<p>{{name}}</p><div><input value="xixi"></div>',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
public name: string = 'childName';

greeting(name: string) {
console.log('hello ' + name);
}
}

father.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ChildComponent } form './child/ChildComponent';

@Component({
selector: 'app-root',
template: '<app-child #child></app-child>'
})
export class AppComponent implements OnInit {
@ViewChild('child')child: ChildComponent;

clickMe() {
this.child.greeting('father');
}
}

第四章:服务

1.创建服务

服务其实也是一个ts文件,只不过创建这个文件命名时要加上service来表明它是一个服务

1.如创建一个名为hero的服务

1
hero.service.ts
1
2
3
4
5
import { Injectable } from '@angular/core';	// 引入Injectable, 就像组件引用Component一样

@Injectable()
export class HeroService {
}

此时创建了一个hero的服务,但是里面什么东西也没有

2.给服务中添加东西

可以给服务中添加一个方法,这个方法用来获取一个heros数组

首先创建一个mock-heroes.ts来存放数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Hero } from './hero';

export const HEROES: Hero[] = [ // 表明数组中的每一个对象都是Hero这个类,并输出这个数组
{ 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' }
];
export let NUM: number = 1 // 也可以输出多个变量(或常量),如再输出一个NUM的变量

然后在服务中引用它,并设定一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Injectable } from '@angular/core';

import { Hero } from './hero';
import { HEROES } from './mock-heroes'; // 接收mock-heroes中的数组
import { NUM } from './mock-heroes'; // 接收mock-heroes中的变量NUM

@Injectable()
export class HeroService {
getHeroes(): Hero[] {
return HEROES;
}
getNum(): number {
return NUM;
}
}

3.使用服务

如在app.component这个组件中要引用这个服务:

app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
import { HeroService } from './hero.service'; // 1. 引入服务
...
@Component({
...
providers: [HeroService]; // 2. 在@Component组件的元数据底部添加providers数组属性
})
export class AppComponent{
title = 'Tour of Heroes';
heroes: Hero[];
selectedHero: Hero;
num: number;
constructor(private heroService: HeroService) {}; // 3. 添加构造函数

getHeroes ():void {
this.heroes =this.heroService.getHeroes(); // 4. 使用服务中的方法,将获取过来的数组赋值给app中的
};

getNum ():void {
this.num =this.heroService.getNum(); // 4. 使用服务中的方法,将获取过来的NUM赋值给app中的num
}
...
}

经过上面几部,我们实现了引用服务,并且可以使用服务中的方法。

但是我们想要页面一打开就执行getHeroes()这个方法,可以参考生命周期

4. 异步服务与承诺

上面我们的服务使用的是同步的方法来获取数据,可以结合Promise来异步获取数据

服务中的getHeroes方法

同步的写法:

1
2
3
getHeroes(): Hero[] {
return HEROES;
}

异步的写法:

1
2
3
getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES); // 异步返回的是一个promise对象
}

此时也要修改引用服务的地方,如上面的app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
import { HeroService } from './hero.service'; // 1. 引入服务
...
@Component({
...
providers: [HeroService]; // 2. 在@Component组件的元数据底部添加providers数组属性
})
export class AppComponent{
title = 'Tour of Heroes';
heroes: Hero[];
selectedHero: Hero;
num: number;
constructor(private heroService: HeroService) {}; // 3. 添加构造函数

getHeroes ():void {
this.heroService.getHeroes().then(heroes => this.heroes = heroes); // 采用promise的写法
};

getNum ():void {
this.num =this.heroService.getNum(); // 采用同步的写法
}
...
}

第五章: 生命周期

在上面,我们虽然引用了自定义的服务,但是它并没有被调用,可以添加生命周期,让它在适当的时候调用。

生命周期的顺序

当Angular使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法:

钩子 目的和时机
ngOnChanges() 当Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的SimpleChanges对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在ngOnInit()之前。
ngOnInit() 在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。在第一轮ngOnChanges()完成之后调用,只调用一次
ngDoCheck() 检测,并在发生Angular无法或不愿意自己检测的变化时作出反应。在每个Angular变更检测周期中调用,ngOnChanges()ngOnInit()之后。
ngAfterContentInit() 当把内容投影进组件之后调用。第一次ngDoCheck()之后调用,只调用一次。只适用于组件
ngAfterContentChecked() 每次完成被投影组件内容的变更检测之后调用。ngAfterContentInit()和每次ngDoCheck()之后调用只适合组件
ngAfterViewInit() 初始化完组件视图及其子视图之后调用。第一次ngAfterContentChecked()之后调用,只调用一次。只适合组件
ngAfterViewChecked() 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次ngAfterContentChecked()之后调用。只适合组件
ngOnDestroy 当Angular每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在Angular销毁指令/组件之前调用。

在上面的例子中,我们就可以在ngOnInit()中使用服务的方法

使用生命周期钩子

1
2
3
4
5
6
7
8
9
import { Component, OnInit } from '@angular/core'; // 1. 引入生命周期钩子

export class AppComponent implements OnInit{ // 2. 往export语句中添加OnInit接口的实现:

ngOnInit(): void { // 3. 初始化逻辑的ngOnInt()方法
this.getHeroes();
}

}

此时我们的案例变成了:

app.component.ts

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
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [HeroService]
})
export class AppComponent implements OnInit{
title = 'Tour of Heroes';
heroes: Hero[];
selectedHero: Hero;
num: number;
constructor(private heroService: HeroService) {};

getHeroes ():void {
this.heroService.getHeroes().then(heroes => this.heroes = heroes);
};

getNum ():void {
this.num =this.heroService.getNum()
}

ngOnInit(): void {
this.getHeroes();
this.getNum();
}

onSelect(hero: Hero):void {
this.selectedHero = hero;
}
}

第六章: 路由

1. 配置路由

1.在app.module.ts中引用

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
...
import { RouterModule } from '@angular/router'; // 1. 引入router
...

import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes.component'; // 2. 引入要路由的组件
import { DashboardComponent } from './dashboard.component'; // 2. 引入要路由的组件

@NgModule({
declarations: [ // 3. 引入要路由的组件
AppComponent,
HeroesComponent,
DashboardComponent
],
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([ // 4. 添加到AppModule的imports数组中
{
path: 'heroes',
component: HeroesComponent
},
{
path: 'dashboard',
component: DashboardComponent
},
])
],
bootstrap: [AppComponent]
})
export class AppModule { }

2. 使用路由

2.在app.component.html中使用

1
2
3
4
5
6
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>

3. 路由重定向

在配置路由路径的时候

当路径是空的时候自动重定向到/dashboard

1
2
3
4
5
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
}

4. 参数化路由

添加一个组件,用来显示英雄的详细信息,通过给路由中传递参数来跳转到对应的英雄

1.配置参数路由

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

import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component'; // 2.
import { HeroesComponent } from './heroes.component';
import { HeroService } from './hero.service';
import { DashboardComponent } from './dashboard.component';

@NgModule({
declarations: [
AppComponent,
HeroDetailComponent, // 3.
HeroesComponent,
DashboardComponent
],
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{
path: 'heroes',
component: HeroesComponent
},
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'detail/:id', //4.
component: HeroDetailComponent
}
])
],
providers: [HeroService],
bootstrap: [AppComponent]
})
export class AppModule { }

2.使用参数路由

dashboardapp下的一个路由

路径为localhost:4200/dashboard

该组件只显示前4个英雄,当点击英雄,跳转到英雄详情页

dashboard.component.html:

1
2
3
4
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" [routerLink] = "['/detail', hero.id]" class="col-1-4">{{hero.name}}</a>
</div>

dashboard.component.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component, OnInit } from '@angular/core';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
selector: 'my-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit{
heroes: Hero[] = [];

constructor(private heroService: HeroService) {}

ngOnInit(): void {
this.heroService.getHeroes()
.then(heroes => this.heroes = heroes.slice(1, 5));
}
}

这一步是引入heroesService这个自定义的服务,来获取英雄列表,详情请看《第四章: 服务》

3.英雄详情页

hero-detail.component.html

1
2
3
4
5
6
7
8
9
10
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="goBack()">Back</button>
</div>

hero-detail.component.ts

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
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router'; // 1.
import { Location } from '@angular/common'; // 2.
import 'rxjs/add/operator/switchMap'; // 3.

import { Hero } from '../hero';
import { HeroService } from '../hero.service';

@Component({
selector: 'hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit{
hero: Hero;
constructor(
private heroService: HeroService,
private route: ActivatedRoute, // 4.
private location: Location // 5.
) {}

ngOnInit(): void {
this.route.paramMap // 6.
.switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back(); // 7. 返回上一层
}
providers: [HeroService]
}

4.配置自定义服务heroService中的getHero()方法:

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
import { Injectable } from '@angular/core';

import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { NUM } from './mock-heroes';
@Injectable()
export class HeroService {
// 获取全部英雄
getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES);
}
// 获取数字
getNum(): number {
return NUM
}
// 延迟获取全部英雄
getHeroesSlowly(): Promise<Hero[]> {
return new Promise(resolve => {
// Simulate server latency with 2 second delay
setTimeout(() => resolve(this.getHeroes()), 2000);
});
}
// 获取指定的英雄
getHero(id: number): Promise<Hero> {
return this.getHeroes()
.then(heroes => heroes.find(hero => hero.id === id));
}
}

此时点击英雄列表的一个英雄,跳转到详情页

1
http://localhost:4200/detail/12

5. js进行路由跳转

上面我们实现的路由跳转是通过[routerLink] = "['/detail', hero.id]"来进行路由跳转的,但如果想要给一个按钮添加点击事件进行跳转,怎么办呢?可以调用路由器的navigate()方法:

如在heroes.component.html中添加一个按钮:

1
2
3
4
5
6
<div *ngIf="selectedHero">
<h2>
{{selectedHero.name | uppercase}} is my hero
</h2>
<button (click)="gotoDatail()">View Details</button> // 添加点击事件
</div>

heroes.component.ts中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; // 1. 引入路由
import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
selector: 'my-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css'],
})
export class HeroesComponent implements OnInit{
...
constructor(
private heroService: HeroService,
private router: Router // 2.
) {};
...
gotoDatail():void { // 3. 调用router中的navigate()
this.router.navigate(['/detail', this.selectedHero.id]);
}
}

若是跳转无参数的路由,也是可以的:

1
2
3
gotoDatail():void {
this.router.navigate(['/dashboard']);
}

6. 重构路由为一个路由模板

可以将路由重新定义为一个模块,在app.module.ts中引用

app-routing.module.ts

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

import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './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 {}

而此时app.module.ts中要进行修改:

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

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

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

@NgModule({
declarations: [
AppComponent,
HeroDetailComponent,
HeroesComponent,
DashboardComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule // 2.
],
providers: [HeroService],
bootstrap: [AppComponent]
})
export class AppModule { }

第七章: 管道

7.1 使用管道

如:

1
2
3
4
5
6
7
8
9
10
11
12
import { Component } from '@angular/core';

@Component({
selector: 'app-hero-birthday',
template: '<p>{{ birthday }}</p>'
})
export class HeroBirthdayComponent {
birthday = new Date(1988, 3, 15);
}

// 不使用管道时,显示:
// Fri Apr 15 1988 00:00:00 GMT+0800 (中国标准时间)

使用管道后:

1
2
<p>{{ birthday | date }}</p>
// Apr 15, 1988

7.2 参数化管道

上面的案例中,使用的是data管道,来进行时间转换,有其默认的转换格式,我们也可以自定义转换格式,只需要像data管道中传递参数就可以了。

比如我们想要上面的信息转换为1998/03/15的格式

1
<p>{{ birthday | date:"yyyy/MM/dd" }}</p>

7.3 自定义管道

angular4还支持自定义管道,你可以自己定义想要的管道。

近期接到这样一个需求:

要求在多个页面中显示用户的头像图片,但有的用户没有上传头像的话,就需要系统根据用户性别显示默认的男/女头像。

当然这样一个简单的需求无论你是在HTML进行判断还是在js中判断都可以实现。

当若是有多个页面都需要用到的话,似乎也是一项比较繁重的任务。

所以你可以选择封装一个共用的方法在每个页面进行调用或者可以尝试自定义一个管道

博主工作中使用的是前端框架Angularjs4,项目整体是使用angular-cli进行搭建的,下面介绍的是如何在angularjs中自定义一个管道。

1.确定需求

1
2
3
4
多个页面显示用户头像
若是头像图片地址不存在则判断用户性别
根据用户性别显示默认的男女头像
若是性别和头像图片地址都不存在则显示默认的人形头像

2.设计管道

1.前期准备

在项目目录src/app/common文件夹下创建一个新文件夹pipe,并在pipe中创建一个comm.pipe.ts文件。

(common文件夹是我存放一些公共组件/方法/管道的文件夹,它是一个功能的模块,其中的所有组件/方法/管道我都会在common文件夹下的shared.module.ts中进行导出)

2.编写comm.pipe.ts

自定义管道需要先引入@angular/core中的PipePipeTransform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//comm.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

const sexList = ['', '男', '女'];
const uHeadImg = ['./assets/images/default_male.png', './assets/images/boy.png', './assets/images/girl.png'];

@Pipe({ name: 'portrait' })
export class Portrait implements PipeTransform {
transform(value): string {
let url = "";
if (value === '男' || value === '女') {
let idx = sexList.indexOf(value);
url = uHeadImg[idx];
} else {
url = value ? value : uHeadImg[0];
}
return url;
}
}
  • 定义俩个数组一个为性别,一个为三种图片的存放路径
  • 需要使用@Pipe来装饰类
  • 实现PipeTransform的transform方法,该方法接受一个输入值和一些可选参数
  • 在@Pipe装饰器中指定管道的名字,这个名字就可以在模板中使用。
  • transform为PipeTransform中继承而来的方法,它接收0个或多个参数

3.导出自定义管道

shared.module.ts中导出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//shared.module.ts

import { NgModule } from '@angular/core';
import { Portrait } from './pipe/comm.pipe';
...

@NgModule({
imports: [
...
],
declarations: [
...
Portrait
],
exports: [
...
Portrait
]
})
export class SharedModule {

}

4.使用管道

由于管道是在shared.module.ts中导出的,因此要使用它就必须在要使用的模块中导入

如在student这个模块中使用

1.首先在student.module.ts中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//student.module.ts

import { NgModule } from '@angular/core';
import { SharedModule } from "./../common/shared.module";

import { Students } from "./students"; //students为该模块下的一个页面

@NgModule({
imports: [
SharedModule,

],
declarations: [
Students
],
providers: [
]
})
export class StudentModule { }

2.在页面中使用:

1
2
3
4
5
6
//students.component.html

<div class="uHead">
<span>学员头像:</span>
<img src="{{studentInfo['uHeadUrl']||studentInfo.sex | portrait}}" title="头像" alt="头像">
</div>
  • studentInfo.uHeadUrl||studentInfo.sex就是传递给管道的参数,表示为如果有头像路径则传递头像路径,没有则传递性别。
  • | potrait`表示使用名为potrait的管道,就是你在comm.pipe.ts中定义的name

5.总结

angularjs4中使用管道总结为这么几步:

1.定义一个自定义管道的ts并引入@angular/core中的Pipe来编写管道

2.将自定义管道的ts在模块中导出

3.要使用管道的模块中引入管道模块

4.html中使用的话采用以下方式:

1
{{ info | PipeName }}  // PipeName为你自定义的管道名称

第八章: HTTP

8.1 配置HttpModule模块

我们的应用将会依赖于 Angular 的http服务,它本身又依赖于其它支持类服务。 来自@angular/http库中的HttpModule保存着这些 HTTP 相关服务提供商的全集。

我们要能从本应用的任何地方访问这些服务,就要把HttpModule添加到AppModuleimports列表中。 这里同时也是我们引导应用及其根组件AppComponent的地方。

1.在app.moudule.ts中引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
import { HttpModule } from '@angular/http'; // 1.
...

@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule, // 2.
AppRoutingModule
],
declarations: [
AppComponent,
DashboardComponent,
HeroDetailComponent,
HeroesComponent,
],
providers: [ HeroService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

8.2 模拟web API

在拥有一个能处理Web请求的服务器之前,我们可以先用HTTP客户端通过一个模拟(Mock)服务(内存Web API)来获取和保存数据。

app.module.ts中修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; // 1.
import { InMemoryDataService } from './in-memory-data.service';

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

@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
InMemoryWebApiModule.forRoot(InMemoryDataService), // 2.
AppRoutingModule
],
})
export class AppModule { }

app下创建一个in-memory-data.service.ts文件:

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: 0, name: 'Zero' },
{ 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};
}
}

这个文件已经替换了mock-heroes.ts,可以删除mock-heroes.ts

但有上面那些步骤还是不够的,此时你会发现angular-in-memory-web-api并不能用。

解决步骤:

1.查看工程根目录下的package.json,没有添加angular-in-memory-web-api,于是在”dependencies”下添加"angular-in-memory-web-api": "^0.3.2"

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
{
...
"dependencies": {
"@angular/animations": "^5.0.0",
"@angular/common": "^5.0.0",
"@angular/compiler": "^5.0.0",
"@angular/core": "^5.0.0",
"@angular/forms": "^5.0.0",
"@angular/http": "^5.0.0",
"@angular/platform-browser": "^5.0.0",
"@angular/platform-browser-dynamic": "^5.0.0",
"@angular/router": "^5.0.0",
"angular-in-memory-web-api": "^0.3.2",
"cnpm": "^5.1.1",
"core-js": "^2.4.1",
"echarts": "^3.8.5",
"ng2-echarts": "^0.0.3",
"ngx-echarts": "^2.0.1",
"rxjs": "^5.5.2",
"zone.js": "^0.8.14"
},
"devDependencies": {
...
}
}

2.然后在项目根目录下执行cnpm install安装angular-in-memory-web-api依赖。

3.重新打开项目。或者执行npm start.

8.3 使用http.get()

这里我用的还是第四章设定的heroes.service.ts服务

将原本的从mock-heroes.ts获取heroes改为从我们的模拟web API中获取。

heroes.service.ts

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
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http'; // 1.

import 'rxjs/add/operator/toPromise'; // 2.

import { Hero } from './hero';

@Injectable()
export class HeroService {
private heroesUrl = 'api/heroes'; // 3.
constructor(private http: Http) { } // 4.

// 获取全部英雄
getHeroes(): Promise<Hero[]> {
return this.http.get(this.heroesUrl)
.toPromise()
.then(response => response.json().data as Hero[])
.catch(this.handleError);
}

private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}

刷新浏览器后,英雄数据就会从模拟服务器被成功读取。

使用时,比如在dashboard中获取全部英雄

1
2
3
4
5
6
7
8
9
10
11
import { Component, OnInit } from '@angular/core';
import { HeroService } from './hero.service'; // 1.

export class DashboardComponent implements OnInit {

constructor(private heroService: HeroService) { } // 2.

ngOnInit(): void {
this.heroService.getHeroes() // 3.
.then(heroes => this.heroes = heroes);
}

8.4 url拼接

没用http请求之前,获取英雄详情,我们使用的是:

1
2
3
4
getHero(id: number): Promise<Hero> {
return this.getHeroes()
.then(heroes => heroes.find(hero => hero.id === id));
}

从模拟服务器获取指定英雄数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private heroesUrl = 'api/heroes';  // 1. URL to web api

// 获取指定的英雄
getHero(id: number): Promise<Hero>{ // 2.
const url = this.heroesUrl + '/' + hero.id;
return this.http.get(url)
.toPromise()
.then(response => response.json().data as Hero)
.catch(this.handleError);
}

private handleError(error: any): Promise<any> { // 3.请求错误
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}

8.5 http.put()改

点击英雄列表中的英雄,进入英雄详情页,然后点击保存对英雄信息进行更改:

1.

hero-detail.component.html

1
<button (click)="save()">Save</button>

hero-detail.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { HeroService } from '../hero.service';

export class HeroDetailComponent implements OnInit{
constructor(
private heroService: HeroService,
) {}

save(): void {
this.heroService.upData(this.hero)
.then(() => this.goBack());
}
goBack(): void {
this.location.back();
}
providers: [HeroService]
}

2.在英雄服务中设置:

1
2
3
4
5
6
7
8
9
// 跟新英雄信息
upData(hero: Hero): Promise<Hero>{
const url = this.heroesUrl + '/' + hero.id;
return this.http
.put(url, JSON.stringify(hero), {headers: this.headers})
.toPromise()
.then(() => hero)
.catch(this.handleError);
}

8.6 http.post()增

heroes.component.html

1
2
3
4
5
6
<div>
<label>Hero name:</label> <input #heroName />
<button (click)="add(heroName.value); heroName.value=''">
Add
</button>
</div>

heroes.component.ts (add)

1
2
3
4
5
6
7
8
9
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.create(name)
.then(hero => {
this.heroes.push(hero);
this.selectedHero = null;
});
}

hero.service.ts (create)

1
2
3
4
5
6
7
create(name: string): Promise<Hero> {
return this.http
.post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
.toPromise()
.then(res => res.json().data as Hero)
.catch(this.handleError);
}

8.7 http.delete()删

heroes.component.html (li-element)

1
2
3
4
5
6
7
<li *ngFor="let hero of heroes" (click)="onSelect(hero)"
[class.selected]="hero === selectedHero">
<span class="badge">{{hero.id}}</span>
<span>{{hero.name}}</span>
<button class="delete"
(click)="delete(hero); $event.stopPropagation()">x</button>
</li>

heroes.component.ts (delete)

1
2
3
4
5
6
7
8
delete(hero: Hero): void {
this.heroService
.delete(hero.id)
.then(() => {
this.heroes = this.heroes.filter(h => h !== hero);
if (this.selectedHero === hero) { this.selectedHero = null; }
});
}

hero.service.ts (delete)

1
2
3
4
5
6
7
delete(id: number): Promise<void> {
const url = this.heroesUrl + '/' + id;
return this.http.delete(url, {headers: this.headers})
.toPromise()
.then(() => null)
.catch(this.handleError);
}

评论