angular2
模块
- 每个 Angular 应用至少有一个模块(根模块),习惯上命名为
AppModule
。 - Angular 模块(无论是根模块还是特性模块)都是一个带有
@NgModule
装饰器的类。 NgModule
是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。简单根模块的设置
1
2
3
4
5
6
7
8
9
10import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }bootstrap
表明应用的主视图declarations
表明本模块拥有的视图类(只有组件、指令和管道),自己写的模块应该声明在这个数组中@NgModule
元数据,告诉angular如何去编译和启动应用- 在
main.ts
中引导根模块来启动应用
组件(视图)
我们在类中定义组件的应用逻辑,为视图提供支持。 组件通过一些由属性和方法组成的 API 与视图交互。
命名规范
- 组件的类名应该是大驼峰形式,并且以Component结尾。 类似HeroDetailComponent。
- 组件的文件名应该是小写中线形式,每个单词之间用中线分隔,并且以.component.ts结尾。 因此HeroDetailComponent类应该放在hero-detail.component.ts文件中。
- 模板中的标签名字应该是小写中线的形式
模板
- 通过组件自带的模板来定义组件视图,告诉angular如何渲染组件
- 在模板中可以添加自定义元素,当作子组件
1
2
3
4
5
6
7
8
9
10template: `
<h1>{{title}}</h1>
<h2>My favorite hero is: {{myHero}}</h2>
<p>Heroes:</p>
<ul>
<li *ngFor="let hero of heroes">
{{ hero }}
</li>
</ul>
`
这里注意angular语法,同时template可以用独立的html文件引入,(tempurl)
- 模板引用变量
1
2
3
4
5
6
7
8
9
10
11
12
13@Component({
selector: 'key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values = '';
onKey(value: string) {
this.values += value + ' | ';
}
}
可以从模版当中直接获取输入,注意要绑定事件,只有处理异步事件,才更新
元数据
要告诉angular某个类是一个组件,就需要把元数据附加到这个类,在typescript中使用装饰器来附加元数据
1 | @Component({ |
这里需要用到@Component
装饰器,配置项为:
selector
:查找模板当中的标签,并插入templateUrl
表示模板的相对地址providers
表示所需要的服务,是一个数组
主要思想是想模版中添加元数据,然后angular将其生成组件
数据绑定
单向数据绑定
1
2
3<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>双向数据绑定
1
<input [(ngModel)]="hero.name">
Angular 在每个 JavaScript 事件循环中处理所有的数据绑定,它会从组件树的根部开始,递归处理全部子组件。数据通过属性绑定从组件流向视图(模板),而视图通过事件绑定更新属性值
指令
组件实际上就是一个带模版的指令,还有:
- 结构型指令
1
2
3Copy Code
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>
通过在 DOM 中添加、移除和替换元素来修改布局。
- 属性型指令
1
<input [(ngModel)]="hero.name">
ngModel
指令就是属性型指令的一个例子,它实现了双向数据绑定。 ngModel修改现有元素(一般是<input>
)的行为:设置其显示属性值,并响应 change 事件。
1
2
3export class HeroDetailComponent {
@Input() hero: Hero;
}
当组件中的属性需要在父组件或者其他组件当中进行设置的时候,就应该在当前组件的中声明为属性型指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Component({
selector: 'my-app',
template: `
<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装饰器。
- 当它出现在等号左边的方括号([ ])中时,该属性属于其它组件或指令,它必须带有@Input 装饰器。
服务
- 命名规范
我们遵循的文件命名约定是:服务名称的小写形式(基本名),加上.service后缀。 组件需要的两个服务设置为私有变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15export class HeroService {
private heroes: Hero[] = [];
constructor(
private backend: BackendService,
private logger: Logger) { }
getHeroes() {
this.backend.getAll(Hero).then( (heroes: Hero[]) => {
this.logger.log(`Fetched ${heroes.length} heroes.`);
this.heroes.push(...heroes); // fill cache
});
return this.heroes;
}
}异步调用的服务类型
1
2
3
4
5
6@Injectable()
export class HeroService {
getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES);
}
}定义一个promise
1
2
3getHeroes(): void {
this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}在组件当中对方法的实现,传入一个成功时候的回调函数,进行数据的设置
组件应当保持精简,同时应该将所有的操作都委托给服务去做,主要是为数据绑定提供属性和方法。
依赖注入
使用依赖注入来提供组件所需要的服务解决依赖问题
- 依赖注入有助于解决函数构建相互之间的依赖问题,同时使得类与依赖之间解耦,便于调试
- 通过查看构造函数的参数类型得知组件需要那服务
- 注入器维护了一个服务实例的容器,存放着以前创建的实例。 如果所请求的服务实例不在容器中,注入器就会创建一个服务实例,并且添加到容器中,然后把这个服务返回给 Angular。
关于服务,必须使用注入器为服务注册一个提供商 - 注册在根模块,可以在任何地方都使用同一个实例
- 注册在元数据中的providers属性,表示是组件级别的服务,每一个组件的实例都会有一个新的服务实例
1 | import { Component } from '@angular/core'; |
将构造函数所需要的数据交给一个服务去完成,只需要调用服务的方法
1 | import { Injectable } from '@angular/core'; |
出现多级依赖,将他所以需要的服务添加为私有属性,并在方法的定义中调用
动态创建依赖值
- 带依赖的类提供商(配置)
1 | [ UserService, |
- 值提供商
对值进行配置
1 |
|
注册依赖的提供商1
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
之后将配置对象注入到需要他的构造函数当中去1
2
3constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
表单
- 每个表单的必须定义name属性,angular通过name来注册控件
- ng-valid | ng-invalid,对表单input的提示
1 | <label for="name">Name</label> |
注意这里使用了模板应用变量,通过name是否有效(或者是全新,即没有任何输入的情况),来进行验证的前端判断
同时记得应清除标记
1 | <button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button> |
目录结构理解
对一个angular应用的相应的模块加载和组件的应用
app.component.ts
应用的根组件,通过装饰器引入其他子组件1
2
3
4
5
6import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<hero-form></hero-form>'
})
export class AppComponent { }app.module.ts
应用的根模块,通过ngModule
,对应用的视图组件,主组件,引入的模块进行设置hero-form.component.ts
,子模块,其中定义一个类,其中有数据成员的方法,可以通过其中绑定与视图进行联系1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { Component } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-form',
templateUrl: './hero-form.component.html'
})
export class HeroFormComponent {
powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
submitted = false;
onSubmit() { this.submitted = true; }
newHero() {
this.model = new Hero(42, '', '');
}
}
生命周期钩子
1 | import { OnInit } from '@angular/core'; |
在这个例子当中,是为了保证构造函数功能的单一,因此将方法定义在模块的生命周期钩子中,当初始化时进行函数调用.
路由
在根模块引入抽离出来的路由模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18src/app/app-routing.module.ts
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.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 {}设置相对应的路映射的路由
1
2
3
4
5
6
7
8
9
10
11
12import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<a routerLink="/heroes">Heroes</a>
<router-outlet></router-outlet>
`
})
export class AppComponent {
title = 'Tour of Heroes';
}设置路由器组件,在导航组件当中使用
routerLink
进行url跳转,之后在底部添加router-outlet,相当于一个槽,导引相关子模块,可以有多个routerLink配置带有参数的路由
1
import { ActivatedRoute, Params } from '@angular/router';
然后注入ActivatedRoute和HeroService服务到构造函数中,将它们的值保存到私有变量中:
1
2
3
4
5constructor(
private heroService: HeroService,
private route: ActivatedRoute,
private location: Location
) {}然后再通过switchMap找到通过id映射的数据,在初始化的时候进行操作
1
2
3
4
5ngOnInit(): void {
this.route.params
.switchMap((params: Params) => this.heroService.getHero(+params['id']))
.subscribe(hero => this.hero = hero);
}之后需要将模板中的导航元素换乘a标签,同时绑定了
[routerLink]
属性,通过一个数组来确定映射到哪一个数据1
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
交互跳转
1
2
3
4
5
6gotoDetail(): void {
this.router.navigate(['/detail', this.selectedHero.id]);
}
goBack(): void
this.location.back();
}
HTTP通信
在根模块引入1
import { HttpModule } from '@angular/http';
实现相对应的功能
1 |
|
实现保存(http put方法)
1 | private headers = new Headers({'Content-Type': 'application/json'}); |
Observable(可观察对象)
Http服务中的每个方法都返回一个 HTTP Response
对象的Observable
实例。
1 | private searchTerms = new Subject<string>(); |
- 其中
Subject
是一个可观察事件流中的生产者,也是一个Observable
对象 - 每次调用
search
函数的时候都会把新的字符串放入该主题的可观察流当中 - 当发起一个http请求的时候返回的也是一个可观察对象
实现组件之间的通讯
1 | private missionAnnouncedSource = new Subject<string>(); |
使用一个独立的服务作为共享数据的服务,通过服务注入得到实例
1 | constructor(private missionService: MissionService) { |
- 通过调用服务当中的
subject
对象进行订阅,并设置回调函数,发生变化时自动更新 - 可以将订阅的
subscription
保存下来,在组件销毁的时候进行调用