angular-learning

angular2


模块

  • 每个 Angular 应用至少有一个模块(根模块),习惯上命名为AppModule
  • Angular 模块(无论是根模块还是特性模块)都是一个带有@NgModule装饰器的类。
  • NgModule是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。

    简单根模块的设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { 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
    10
    template: `
    <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
2
3
4
5
6
7
@Component({
selector: 'hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
export class HeroListComponent implements OnInit {
}

这里需要用到@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
    3
    Copy 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
3
export 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
    15
    export 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
    3
    getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
    }

    在组件当中对方法的实现,传入一个成功时候的回调函数,进行数据的设置

组件应当保持精简,同时应该将所有的操作都委托给服务去做,主要是为数据绑定提供属性和方法。

依赖注入

使用依赖注入来提供组件所需要的服务解决依赖问题

  • 依赖注入有助于解决函数构建相互之间的依赖问题,同时使得类与依赖之间解耦,便于调试
  • 通过查看构造函数的参数类型得知组件需要那服务
  • 注入器维护了一个服务实例的容器,存放着以前创建的实例。 如果所请求的服务实例不在容器中,注入器就会创建一个服务实例,并且添加到容器中,然后把这个服务返回给 Angular。
    关于服务,必须使用注入器为服务注册一个提供商
  • 注册在根模块,可以在任何地方都使用同一个实例
  • 注册在元数据中的providers属性,表示是组件级别的服务,每一个组件的实例都会有一个新的服务实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component }   from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
selector: 'hero-list',
template: `
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
`
})
export class HeroListComponent {
heroes: Hero[];
constructor(heroService: HeroService) {
this.heroes = heroService.getHeroes();
}
}

将构造函数所需要的数据交给一个服务去完成,只需要调用服务的方法

1
2
3
4
5
6
7
8
9
10
11
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';
@Injectable()
export class HeroService {
constructor(private logger: Logger) { }
getHeroes() {
this.logger.log('Getting heroes ...');
return HEROES;
}
}

出现多级依赖,将他所以需要的服务添加为私有属性,并在方法的定义中调用

动态创建依赖值

  • 带依赖的类提供商(配置)
1
2
[ UserService,
{ provide: Logger, useClass: EvenBetterLogger }]
  • 值提供商
    对值进行配置
1
2
3
4
5
6
7
8
9
10
11

Copy Code
export interface AppConfig {
apiEndpoint: string;
title: string;
}

export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
};

注册依赖的提供商

1
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

之后将配置对象注入到需要他的构造函数当中去

1
2
3
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}

表单

  • 每个表单的必须定义name属性,angular通过name来注册控件
  • ng-valid | ng-invalid,对表单input的提示
1
2
3
4
5
6
7
8
9
<label for="name">Name</label>
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name"
#name="ngModel">
<div [hidden]="name.valid || name.pristine"
class="alert alert-danger">
Name is required
</div>

注意这里使用了模板应用变量,通过name是否有效(或者是全新,即没有任何输入的情况),来进行验证的前端判断

photo

同时记得应清除标记

1
<button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>

目录结构理解

对一个angular应用的相应的模块加载和组件的应用

  • app.component.ts 应用的根组件,通过装饰器引入其他子组件

    1
    2
    3
    4
    5
    6
    import { 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
    16
    import { 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
2
3
4
5
6
import { OnInit } from '@angular/core';

export class AppComponent implements OnInit {
ngOnInit(): void {
}
}

在这个例子当中,是为了保证构造函数功能的单一,因此将方法定义在模块的生命周期钩子中,当初始化时进行函数调用.

路由

  • 在根模块引入抽离出来的路由模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    src/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
    12
    import { 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
    5
    constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
    ) {}

    然后再通过switchMap找到通过id映射的数据,在初始化的时候进行操作

    1
    2
    3
    4
    5
    ngOnInit(): 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
    6
    gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
    }
    goBack(): void
    this.location.back();
    }

HTTP通信

在根模块引入

1
import { HttpModule }    from '@angular/http';

实现相对应的功能

1
2
3
4
5
6
7
8
9
10

import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

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

实现保存(http put方法)

1
2
3
4
5
6
7
8
9
private headers = new Headers({'Content-Type': 'application/json'});
update(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);
}

Observable(可观察对象)

Http服务中的每个方法都返回一个 HTTP Response对象的Observable实例。

1
2
3
4
5
private searchTerms = new Subject<string>();
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
  • 其中Subject是一个可观察事件流中的生产者,也是一个Observable对象
  • 每次调用search函数的时候都会把新的字符串放入该主题的可观察流当中
  • 当发起一个http请求的时候返回的也是一个可观察对象

实现组件之间的通讯

1
2
3
4
5
6
7
8
9
10
11
12
private missionAnnouncedSource = new Subject<string>();
private missionConfirmedSource = new Subject<string>();
// Observable string streams
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
missionConfirmed$ = this.missionConfirmedSource.asObservable();
// Service message commands
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}
confirmMission(astronaut: string) {
this.missionConfirmedSource.next(astronaut);
}

使用一个独立的服务作为共享数据的服务,通过服务注入得到实例

1
2
3
4
5
6
constructor(private missionService: MissionService) {
missionService.missionConfirmed$.subscribe(
astronaut => {
this.history.push(`${astronaut} confirmed the mission`);
});
}
  • 通过调用服务当中的subject对象进行订阅,并设置回调函数,发生变化时自动更新
  • 可以将订阅的subscription保存下来,在组件销毁的时候进行调用