原文地址:http://victorsavkin.com/post/118372404541/the-core-concepts-of-angular-2
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.title}}
{{talk.speaker}}
<formatted-rating [rating]="talk.rating">formatted-rating>
<watch-button [talk]="talk">watch-button>
<rate-button [talk]="talk">rate-button>
属性和事件绑定
一个组件具有的属性和事件绑定,这些内容在组件的decorator定义。
@Component({
selector: 'talk-cmp',
properties: ['talk'],
events: ['rate']
})
...
数据通过组件上的属性绑定流入组件,数据通过组件上的事件绑定流出组件
当你在应用程序中实例化一个组件则可以通过公共API使用属性和事件绑定
一个组件有一个视图,描述了在页面上怎么呈现组件
@View({
directives: [FormattedRating, WatchButton, RateButton],
templateUrl: 'talk_cmp.html'
})
{{talk.title}}
{{talk.speaker}}
<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, WatchButton, RateButton],
template: `
`
})
...
组件有一个定义良好的生命周期可以利用(onChange、onInit、onCheck、onAllChangesDone)。该TalkCmp组件不订阅任何生命周期事件,但一些其他组件可以。例如,该组件发生改变时会触发起change事件。
@Component({
selector: 'cares-about-changes',
properties: ['field1', 'field2'],
lifecycle: [onChange]
})
class CareAboutChanges {
field1;
field2;
onChange(changes) {
//..
}
}
一个组件可以包含一个注入对象列表,它的子组件可能也需要注入
@Component({
selector: 'conf-app',
viewInjector: [ConfAppBackend, Logger]
})
class TalksApp {
//...
}
class TalksCmp {
constructor(backend:ConfAppBackend) {
//...
}
}
在这个例子中,我们在根组件中声明ConfAppBackend,和Logger,这使得它们在整个应用程序可用,TalksCmp组件注入ConfAppBackend,我将在本文的第二部分详细讨论依赖注入
要将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 2提供了这样的API,但我们的希望是,你很少会需要使用它们。
我所列出的组件构成.
在Angular2中所有的这些组件都具有自描述性,所以组件的注释(annotations)包含它们的实例所需要的所有信息。这是非常重要的。
这意味着任何组件可以引导作为应用程序,它并不需要任何特殊的方式,此外,任何组件可以被加载到一个router-outlet(ng-view)。因此,您可以编写一个应用程序组件,可被引导(bootstrap),加载路由(route),或直接用于其他组件,这将导致更少的API来学习。同时也让更多的组件可重用。
如果您熟悉angular 1,你一定想知道“指令发生了什么变化”。
其实指令还在Angular 2中,组件只是指令中的最重要的一种,但不是唯一的指令类型,一个组件是一个指令,一个视图,你仍然可以编写一个没有视图的装饰器风格(decorator-style)的指令
让我们来谈论Angular 的另一个重要基石,依赖注入。
依赖注入背后的想法很简单,如果有一个依赖于一个服务的组件。您无需自己创建服务并提供给组件,相反,你可以在构造函数中申请该服务,框架将会自动提供给你该服务,通过这样做,你可以依赖接口,而不是具体类型,这将导致更多的代码解耦,使可测试性和其他更好的事情。
Angular 2配备了依赖注入模块,看它如何被使用,让我们来看看下面的组件,该指令渲染一个talks的列表
@Component({selector: 'talk-list'})
@View({templateUrl: 'talks.html', directives: [NgFor]})
class TalkList {
constructor() {
//..get the data
}
}
<h2>Talks:h2>
<div *ng-for="var t of talks">
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();
}
}
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) {
}
}
依赖注入是你可能马上看不到的好处之一,但当你的应用程序增长更大时,它就越重要
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",
}
talks: [
{
title: "Are we there yet?",
speaker: "Rich Hickey",
yourRating: null,
avgRating: 9.9
}
]
}
如果我必须找到所有可能有改变的地方并手动更新他们,那将是很繁琐且易出错,我想要应用程序自动来反映这一变化,这就是属性绑定。
在Angular虚拟机一轮结束,它会检查每个组件的组件树。更具体地说,它会检查每一个属性绑定(每一个方括号,每一对大括号),并将更新组件。它还将更新DOM来匹配组件树的状态。
在Angular 1 你必须使用scope.$apply来告诉框架需要检查更新的内容,在Angular 2中你不必担心这个问题,Angular 2使用Zone.js知道当前检查是必须得,这意味着在使用第3方库集成时不需要在调用scope.$apply方法