原文地址: http://victorsavkin.com/post/118372404541/the-core-concepts-of-angular-2
让我们来构建一个程序
组件(Component)
Angular 2的应用是由一系列的组件构成的(ui element、route..),应用始终有一个包含其他组件的根组件,换句话每个angualr2应用都有一个组件树,这个应用程序可能是这样的:
Application是一个根组件,Filters组件具有speaker输入框和过滤按钮,下面有一系列的talks,及每一个talk-cmp
// TalkCmp.ts
@Component({
selector: 'talk-cmp',properties: ['talk'],events: ['rate']
})
@View({
directives: [FormattedRating,WatchButton,RateButton],templateUrl: 'talk_cmp.html'
})
class TalkCmp {
talk: Talk;
rate: EventEmitter;
//...
}
<!-- talk_cmp.html --> {% raw %} {{talk.title}} {{talk.speaker}} {% endraw %} <formatted-rating [rating]="talk.rating"></formatted-rating> <watch-button [talk]="talk"></watch-button> <rate-button [talk]="talk"></rate-button>
属性和事件绑定
一个组件具有的属性和事件绑定,这些内容在组件的decorator定义。
@Component({
selector: 'talk-cmp',events: ['rate']
})
...
数据通过组件上的属性绑定流入组件,数据通过组件上的事件绑定流出组件
当你在应用程序中实例化一个组件则可以通过公共API使用属性和事件绑定
视图(View)
一个组件有一个视图,描述了在页面上怎么呈现组件
@View({
directives: [FormattedRating,WatchButton,RateButton],templateUrl: 'talk_cmp.html'
})
<!-- talk_cmp.html --> {% raw %} {{talk.title}} {{talk.speaker}} {% endraw %} <formatted-rating [rating]="talk.rating"></formatted-rating> <watch-button [talk]="talk"></watch-button> <rate-button [talk]="talk"></rate-button>
Angular 2遵循web平台标准,因此组件视图元素会在Shadow DOM内创建,如果你的浏览器不支持Shadow DOM,Angular会模拟Shadow DOM.
一个视图需要知道两件事情:模板本身及模板中可使用的指令。如在上面的例子中,你可以在外部定义模板,使用templateUrl,或内联模板template。
...javascript @View({ directives: [FormattedRating,template: ` {{talk.title}} {{talk.speaker}} <formatted-rating [rating]="talk.rating"></formatted-rating> <watch-button [talk]="talk"></watch-button> <rate-button [talk]="talk"></rate-button> ` }) ...
生命周期(LIFECYCLE)
组件有一个定义良好的生命周期可以利用(onChange、onInit、onCheck、onAllChangesDone)。该TalkCmp组件不订阅任何生命周期事件,但一些其他组件可以。例如,该组件发生改变时会触发起change事件。
@Component({
selector: 'cares-about-changes',properties: ['field1','field2'],lifecycle: [onChange]
})
class CareAboutChanges {
field1;
field2;
onChange(changes) {
//..
}
}
注入(INJECTABLES)
一个组件可以包含一个注入对象列表,它的子组件可能也需要注入
@Component({
selector: 'conf-app',viewInjector: [ConfAppBackend,Logger]
})
class TalksApp {
//...
}
class TalksCmp {
constructor(backend:ConfAppBackend) {
//...
}
}
在这个例子中,我们在根组件中声明ConfAppBackend,和Logger,这使得它们在整个应用程序可用,TalksCmp组件注入ConfAppBackend,我将在本文的第二部分详细讨论依赖注入
HOST元素(HOST ELEMENT)
要将Angular组件渲染成DOM中的某种东西,你需要在Angular组件中结合一个DOM元素,我们称这些叫host元素。
一个组件可以用以下方式于其host DOM元素进行交互
例如,组件可以使用host事件监听输入,对输入值进行处理及将其存储在一个字段中,angular会于DOM同步已存储的值
@Component({
selector: 'trimmed-input',host: {
'(input)': 'onChange($event.target.value)','[value]': 'value'
}
})
class TrimmedInput {
value: string;
onChange(updatedValue: string) {
this.value = updatedValue.trim();
}
}
请注意,我真得直接与DOM交互。Angular2旨在提供一个更高层次的API,所以在原生平台(native platform),DOM,只会反映angular应用程序的状态。
这里几个原因非常有用:
- 它使你的应用更容易看懂
- 它允许应用程序的大多数单元测试行为不接触DOM,这样的测试更容易编写和理解。此外,它们要快得多
- 允许angular 应用运行在web worker上
- 它允许运行angular应用在浏览器之外的其他平台上,例如利用NativeScript
有时候,你只需要直接与DOM进行交互。Angular 2提供了这样的API,但我们的希望是,你很少会需要使用它们。
组件是自描述性(COMPONENTS ARE SELF-DESCRIBING)
我所列出的组件构成.
- 一个组件知道如何与它的host元素交互
- 一个组件知道如果来渲染组件,所以它们知道自己的视图
- 一个组件可以配置依赖注入
- 一个组件具有定义良好的属性和事件绑定的公共API
在Angular2中所有的这些组件都具有自描述性,所以组件的注释(annotations)包含它们的实例所需要的所有信息。这是非常重要的。
这意味着任何组件可以引导作为应用程序,它并不需要任何特殊的方式,此外,任何组件可以被加载到一个router-outlet(ng-view)。因此,您可以编写一个应用程序组件,可被引导(bootstrap),加载路由(route),或直接用于其他组件,这将导致更少的API来学习。同时也让更多的组件可重用。
指令是什么?(WHAT ABOUT DIRECTIVES?)
如果您熟悉angular 1,你一定想知道“指令发生了什么变化”。
其实指令还在Angular 2中,组件只是指令中的最重要的一种,但不是唯一的指令类型,一个组件是一个指令,一个视图,你仍然可以编写一个没有视图的装饰器风格(decorator-style)的指令
依赖注入(DEPENDENCY INJECTION)
让我们来谈论Angular 的另一个重要基石,依赖注入。
依赖注入背后的想法很简单,如果有一个依赖于一个服务的组件。您无需自己创建服务并提供给组件,相反,你可以在构造函数中申请该服务,框架将会自动提供给你该服务,通过这样做,你可以依赖接口,而不是具体类型,这将导致更多的代码解耦,使可测试性和其他更好的事情。
Angular 2配备了依赖注入模块,看它如何被使用,让我们来看看下面的组件,该指令渲染一个talks的列表
@Component({selector: 'talk-list'})
@View({templateUrl: 'talks.html',directives: [NgFor]})
class TalkList {
constructor() {
//..get the data
}
}
<!-- talks.html --> <h2>Talks:</h2> <div *ng-for="var t of talks"> {{t.name}} </div>
我们模拟一个简单的服务,会提供给我们数据
class TalksAppBackend {
fetchTalks() {
return [
{ name: 'Are we there yet?' },{ name: 'The value of values' }
];
}
}
我们如何使用这项服务?一种方法是在我们的组件创建该服务的一个实例。
class TalkList {
constructor() {
var backend = new TalksAppBackend();
this.talks = backend.fetchTalks();
}
}
这是一个不错的演示应用程序,但对于实际应用并不好,在实际应用中TalksAppBackend将不只是返回对象的数组,它会发出HTTP请求来获取数据,这意味着在单元测试中这个组件会创建真实的http-requrest(这不是一个好主意),这个问题是由已经耦合TalkList到TalksAppBackend这一事实引起的
我们可以通过注入一个实例TalksAppBackend到构造函数解决这个问题,所以我们可以把它在测试中轻易更换,就像这样:
class TalkList { constructor(backend:TalksAppBackend) { this.talks = backend.fetchTalks(); } }
这告诉了angular TalksList 依赖于TalksAppBackend,现在我们需要告诉Angular如果创建依赖,我们可以为此组件通过添加viewInjector属性
@Component({
selector: 'talk-list',viewInjector: [TalksAppBackend]
})
class TalkList {
constructor(backend:TalksAppBackend) {
this.talks = backend.fetchTalks();
}
}
该TalksAppBackend服务必须在TalkList组件或其祖先指定,所以,如果你喜欢写用Angular 1编写应用方式,你可以在根组件配置你的所有注射服务
@Component({
selector: 'talk-app',viewInjector: [TalksAppBackend] // registered in the root component,so it can be injected into any component in the app.
})
class Application {
}
@Component({
selector: 'talk-list'
})
class TalkList {
constructor(backend:TalksAppBackend) {
this.talks = backend.fetchTalks();
}
}
(单一API)SINGLE API
Angular 1和Angular 2 都配备了依赖注入的模块,但在Angular 1,我们有几个API来注入依赖到指令:有的对象是按位置(例如,元素)注入,有的按名称,这有点混乱。Angular 2提供了注入依赖的单一的API,他们全部在组件的constrocutor中注入。
例如,此组件注入TalksAppBackend(这很可能是一个单列),和一个ElementRef,这是唯一的每个组件的一个实例。
class TalksList { constructor(elRef:ElementRef,backend:TalksAppBackend) { } }
所以我们通过相同的API将全局和局部依赖注入到组件中。此外,组件可以使用相同API注入到其他组件中
class Component { constructor(sibling:SiblingCmp,@Parent parent:ParentCmp,@Ancestor ancestor:AncestorCmp) { } }
依赖注入是你可能马上看不到的好处之一,但当你的应用程序增长更大时,它就越重要
(属性绑定)PROPERTY BINDINGS
Angular 使用属性绑定与组件树上的MODEL和DOM自动同步,要理解为什么这是很重要的,让我们来看看这个应用程序。
我们知道,这个应用程序将会有一个组件树。除了这棵树,它还将有一个模型。我们说这是简单的JavaScript对象,如下所示:
{
filters: {
speaker: "Rich Hickey",}
talks: [
{
title: "Are we there yet?",speaker: "Rich Hickey",yourRating: null,avgRating: 9.0
}
]
}
现在,想象一下一个事件改变model。我很喜欢它的talks,我给它9.9。
{
filters: {
speaker: "Rich Hickey",avgRating: 9.9
}
]
}
如果我必须找到所有可能有改变的地方并手动更新他们,那将是很繁琐且易出错,我想要应用程序自动来反映这一变化,这就是属性绑定。
在Angular虚拟机一轮结束,它会检查每个组件的组件树。更具体地说,它会检查每一个属性绑定(每一个方括号,每一对大括号),并将更新组件。它还将更新DOM来匹配组件树的状态。
ZONES
在Angular 1 你必须使用scope.