巨大变化
在 Angular1 中最重要的两个概念:
- directive 拓展DOM功能,封装DOM操作,并且是可复用的组件
- controller 创建新的 $scope 作用域,封装和DOM无关的业务逻辑
Angular2 是一次彻底的重写,完全删掉了 Controller 和 $scope,增加了一个重要的功能叫 @Component,Component 是Angular2的核心,相当于Angular1 中 Directive 和 Controller 的合体。
并且 directive 功能简化, 和 Angular1 中的 Directive 完全不可以相提并论。
Angular2 用 TypeScript 写的,TypeScript 可以简单理解为 ES6 + Annotation + 强类型。
不过注意截止到今天(2015-11-20) angular2依然是Alpha 版本,也就是说API 还不稳定,还有很多bug,一些功能也没有完全实现。所以Beta版本出来之前不建议生产环境使用。
Angular 2 的一个Component 是这样的:
import {Component, bootstrap} from 'angular2/angular2';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>My favorite hero is: {{myHero}}</h2>
`
})
export class AppComponent {
title = 'Tour of Heroes';
myHero = 'Windstorm';
}
bootstrap(AppComponent);
其中 @Component 是一个annotation,Java中有一个类似的翻译为注解。
上面是一个基本的Angular2 Component。分为这几部分:
- import 需要使用的模块
- 创建了一个Component,一个Component由注解和类两部分组成。@Component 是对 AppComponent 的一个注解,可以理解为对AppComponent的一个配置,声明了这个类是一个Component,并且对他做了一些配置。
- 引导这个Component。其实就是在html中找到对应的选择器并创建和绑定一个实力。
Component
Angular2 中把一块逻辑封装成一个 Component 类。 Component即负责渲染模板和监听事件,也封装业务逻辑。
一个 Component 由两部分组成:
- @Component 注解,这里声明了很多配置项,比如: selector, template, directives, styles, pipes 等。
- 一个普通的类,这个类封装了Component的所有业务逻辑,比如事件回调,数据处理等。
前面有写过ES6的博客,Annotation并不是ES6规范的一部分,但是ES7中的 decorator 其实和 Annotation应该是同一个东西,都是decorator模式。关于ES7的decorator参见这里 https://github.com/wycats/javascript-decorators/blob/master/README.md。
虽然 directive 和 Angular1 中的有很大不同,但是基本的一些内置directive除了写法变了基本还是大同小异的,在模板中的用法也基本不变,比如这个循环:
<ul>
<li *ng-for="#hero of heroes">
{{ hero }}
</li>
</ul>
不过Angular2严格的模块化组织代码,所以用到任何功能都需要先 import:
import {Component, bootstrap, NgFor} from 'angular2/angular2';
并且然后还需要在 注解中声明才可以用。
Angular2 提供了 常见的 NgFor, NgIf 等流程控制directive。双向绑定的directive 还是叫 NgModel ,它属于Form的一部分,Angular2还是专门对form做了很多工作。
DI 依赖注入
依赖注入分两部分,一个是提供,一个是使用。
在Angular2 中通过 @Injectable 来声明一个可以被注入的模块:
@Injectable()
class HeroService {
heroes: Hero[];
constructor(private logger: Logger) {
this.heroes = HEROES;
}
getHeroes() {
this.logger.log('Getting heroes ...')
return this.heroes;
}
}
这样我们就提供了一个 可以被注入的模块。
使用的时候 会稍微麻烦一些,使用的时候分两步。第一步是在自己的Component的构造函数中声明要注入的模块:
constructor(heroService: HeroService) {
this.heroes = heroService.getHeroes();
}
这里根据参数的类型申明就知道需要注入的模块叫 HeroService
第二步比较奇怪一点,就是要声明一个provider。也就是说上面的代码中的 HeroSerivce 我们需要声明是谁提供了它。可能会有人有疑问了,我们不是最开始的时候定义了一个 HeroService 了嘛,为毛还要再声明一次。
这其实是为了灵活性考虑,我们提供了一个 HeroService ,然后在 Component 中又声明了需要注入一个叫 HeroService 的模块。但是注意,其实HeroService 只是一种实现而已,我们完全可以有多种方式来实现 HeroServive。比如我们测试的时候可能希望注入一个 MockHeroService,总不能在测试的时候改掉我们 Component的构造函数吧,最方便的就是我们在测试的时候申明 MockHeroService 就是Component 需要的 HeroService。
基于这个原因,我们需要声明一个 provider 来提供 HeroService 服务。这个声明是放在这里的:
bootstrap(AppComponent, [HeroService]);
奇怪了,不是没有什么provider吗。其实这是一个简写,[HeroService] 等价于 [provide(HeroService, {useClass:HeroService})]; 也就是默认用同名的类来提供。
注意 useClass,表示从这个类创建一个新的实例,不过这个实例是单例的。 也可以不用创建,直接给一个值,这个时候要用 useValue.
Pipe
Pipe 是一个数据处理管道。主要作用就是对数据输出的时候进行一些格式化处理。一个基本的date 管道的用法如下:
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} /p>
其中 竖线是调用管道的语法,date是管道名字,后面的字符串是传入的参数。因为是管道,所以可以把多个管道连接起来:
The chained hero's birthday is {{ birthday | date | uppercase}}
我们可以自定义pipe。一个自定义的pipe的作用就是接受一个输入值和参数,然后返回一个处理后的值。
import {Pipe} from 'angular2/angular2';
/*
* Raise the value exponentially
* Takes an exponent argument that defaults to 1.
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10}}
* formats to: 1024
*/
@Pipe({
name: 'exponentialStrength'
})
export class ExponentialStrengthPipe {
transform(value:number, args:string[]) : any {
return Math.pow(value, parseInt(args[0] || '1', 10));
}
}
这里我们同样会用到annotation来声明一个管道。
代码
贴一段代码,这是官方的教程的,不过官方教程没写完,我加入了一个依赖注入的代码:
app.ts:
import {bootstrap, Component, FORM_DIRECTIVES, CORE_DIRECTIVES} from 'angular2/angular2';
import {HeroService, Hero} from "./heros";
@Component({
selector: 'my-app',
template: `
<h1>My Heros</h1>
<ul>
<li *ng-for="#hero of heroes" (click)="onSelect(hero)" [ng-class]="getSelectedClass(hero)">{{hero.id}}:{{hero.name}}</li>
</ul>
<div *ng-if="selectedHero" >
<h2>{{selectedHero.name}} details!</h2>
<div>
id: {{selectedHero.id}}
<div>
<label>name: </label>
<input [(ng-model)]="selectedHero.name" placeholder="name" />
</div>
</div>
`,
styles: [`
.selected {
background: blue;
color: white;
}
`],
directives: [FORM_DIRECTIVES, CORE_DIRECTIVES]
})
class AppComponent {
public title = 'Tour of Heroes';
public heroes;
public selectedHero: Hero;
constructor(heroService: HeroService) {
this.heroes = heroService.getHeroes();
}
public onSelect(hero: Hero) {
this.selectedHero = hero;
}
public getSelectedClass(hero: Hero) {
return { "selected" : hero == this.selectedHero }
}
}
bootstrap(AppComponent, [HeroService]);
heros.ts:
import {Injectable} from 'angular2/angular2';
@Injectable()
export class HeroService {
getHeroes() {
var HEROES: 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" }
];
return HEROES;
}
}
export class Hero {
id: number;
name: string;
}
参考: