目录
从使用模型-视图-控制器 (MVC) 或模型-视图-视图模型 (MVVM) 的经验中,很多开发人员都熟悉了组件和模板这两个概念。 在 Angular 中,组件扮演着控制器或视图模型的角色,模板则扮演视图的角色。
HTML
- HTML 是 Angular 模板的语言。
- 几乎所有的 HTML 语法都是有效的模板语法。但值得注意的例外是
插值表达式
插值表达式可以把计算后的字符串插入到 HTML 元素标签内的文本或对标签的属性进行赋值。
模板表达式
- 模板表达式产生一个值。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。
- 当我们写{{1 + 1}}时,是往插值表达式的括号中放进了一个模板表达式。 在属性绑定中会再次看到模板表达式,它出现在=右侧的引号中,看起来像这样:[property]=”expression”。
表达式上下文
- 模板表达式不能引用全局命名空间中的任何东西。 不能引用window或document。不能调用console.log或Math.max。 它们被局限于只能访问来自表达式上下文中的成员。
- 典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。
- 通常,组件本身就是表达式的上下文,这种情况下,模板表达式会引用那个组件。
- 表达式的上下文可以包括组件之外的对象。模板引用变量就是备选的上下文对象之一。
表达式指南
- 模板表达式除了目标属性的值以外,不应该改变应用的任何状态。
- Angular 执行模板表达式比我们想象的频繁。表达式应该快速结束。当计算代价较高时,应该考虑缓存那些从其它值计算得出的值。
- 模板表达式应该非常简单。虽然可以写出相当复杂的模板表达式,但不要那么去写。
- 最好使用幂等的表达式,因为它没有副作用,并且能提升 Angular 变更检测的性能。
- 在 Angular 的术语中,幂等的表达式应该总是返回完全相同的东西,直到某个依赖值发生改变。
模板语句
- 模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。
- 模板语句将在事件绑定一节看到,它出现在=号右侧的引号中,就像这样:(event)=”statement”。
- 响应事件是 Angular 中“单向数据流”的另一面。 在一次事件循环中,可以随意改变任何地方的任何东西。
- 模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值 (=) 和表达式链 (;和,)。
语句上下文
- 和表达式中一样,语句只能引用语句上下文中 —— 通常是正在绑定事件的那个组件实例。
- 模板语句无法引用全局命名空间的任何东西。它们不能引用window或者document, 不能调用console.log或者Math.max。
- (click)=”onSave()”中的 onSave 就是数据绑定组件实例中的方法。
- 语句上下文可以包含组件之外的对象。模板引用对象就是备选上下文对象之一。 在事件绑定语句中,经常会看到被保留的
$event
符号,它代表触发事件的“消息”或“有效载荷”。
语句指南
和表达式一样,避免写复杂的模板语句。 常规是函数调用或者属性赋值。
绑定语法
根据数据流的方向,可以把所有绑定归为三类。
数据方向 | 语法 | 绑定类型 |
---|---|---|
单向 从数据源到视图目标 |
{{expression}} [target] = “expression” bind-target = “expression” |
插值表达式、Property、Attribute、类、样式 |
单向 从视图目标到数据源 |
(target) = “statement” on-target=”statement” |
事件 |
双向 | [(target)] = “expression” bindon-target = “expression” |
双向 |
新的思维模型
- 模板绑定是通过 property 和事件来工作的,而不是 attribute。
- 在 Angular 的世界中,attribute 唯一的作用是用来初始化元素和指令的状态。 当进行数据绑定时,只是在与元素和指令的 property 和事件打交道,而 attribute 就完全靠边站了。
HTML attribute 与 DOM property 的对比
1. attribute 是由 HTML 定义的。property 是由 DOM (Document Object Model) 定义的。
2. attribute 初始化 DOM property,然后它们的任务就完成了。property 的值可以改变;attribute 的值不能改变。
例如,当浏览器渲染<input type="text" value="Bob">
时,它将创建相应 DOM 节点, 其value property 被初始化为 “Bob”。
当用户在输入框中输入 “Sally” 时,DOM 元素的value property 变成了 “Sally”。 但是这个 HTML value attribute 保持不变。如果我们读取 input 元素的 attribute,就会发现确实没变: input.getAttribute(‘value’) // 返回 “Bob”。
HTML attribute value指定了初始值;DOM value property 是当前值。
disabled
attribute 是另一个古怪的例子。按钮的disabled property 是false,因为默认情况下按钮是可用的。 当我们添加disabled attribute 时,只要它出现了按钮的disabled property 就初始化为true,于是按钮就被禁用了。
添加或删除disabled attribute会禁用或启用这个按钮。但 attribute 的值无关紧要,这就是我们为什么没法通过 仍被禁用这种写法来启用按钮。
设置按钮的disabled property(如,通过 Angular 绑定)可以禁用或启用这个按钮。 这就是 property 的价值。
3. 就算名字相同,HTML attribute 和 DOM property 也不是同一样东西。
绑定目标
数据绑定的目标是 DOM 中的某些东西。 这个目标可能是(元素 | 组件 | 指令的)property、(元素 | 组件 | 指令的)事件,或(极少数情况下) attribute 名。
绑定类型 | 目标 | 范例 |
---|---|---|
Property | 元素的 property 组件的 property 指令的 property |
<img [src] = "heroImageUrl"> <hero-detail [hero]="currentHero"></hero-detail> <div [ngClass] = "{selected: isSelected}"></div> |
事件 | 元素的事件 组件的事件 指令的事件 |
<button (click) = "onSave()">Save</button> <hero-detail (deleteRequest)="deleteHero()"></hero-detail> <div (myClick)="clicked=$event">click me</div> |
双向 | 事件与 property | <input [(ngModel)]="heroName"> |
Attribute | attribute(例外情况) | <button [attr.aria-label]="help">help</button> |
CSS 类 | class property | <div [class.special]="isSpecial">Special</div> |
样式 | style property | <button [style.color] = "isSpecial ? 'red' : 'green'"> |
属性 (property) 绑定
- 当要把视图元素的属性 (property) 设置为模板表达式时,就要写模板的属性 (property) 绑定。
- 最常用的属性绑定是把元素属性设置为组件属性的值。如:
<img [src]="heroImageUrl">
。 - 另一个例子是设置指令的属性:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
。 - 还有另一个例子是设置自定义组件的模型属性(这是父子组件之间通讯的重要途径):
<hero-detail [hero]="currentHero"></hero-detail>
。
单向输入
人们经常把属性绑定描述成单向数据绑定,因为值的流动是单向的,从组件的数据属性流动到目标元素的属性。
绑定目标
- 包裹在方括号中的元素属性名标记着目标属性。如:
<img [src]="heroImageUrl">
的src
属性。 - 有些人喜欢用
bind-
前缀的可选形式,并称之为规范形式:<img bind-src="heroImageUrl">
。 - 目标的名字总是 property 的名字。即使它看起来和别的名字一样。 看到src时,可能会把它当做 attribute。不!它不是!它是 image 元素的 property 名。
- 元素属性可能是最常见的绑定目标,但 Angular 会先去看这个名字是否是某个已知指令的属性名。
- 如果名字没有匹配上已知指令或元素的属性,Angular 就会报告“未知指令”的错误。
消除副作用
- 不能在属性绑定表达式中对任何东西赋值,也不能使用自增、自减运算符。
- 当然,表达式可能会调用具有副作用的属性或方法。但 Angular 没法知道这一点,也没法阻止我们。
- 一般建议是,只绑定数据属性和那些只返回值而不做其它事情的方法。
注意点
- 模板表达式应该返回目标属性所需类型的值。 如果目标属性想要个字符串,就返回字符串。 如果目标属性想要个数字,就返回数字。 如果目标属性想要个对象,就返回对象。
- 方括号告诉 Angular 要计算模板表达式。 如果忘了加方括号,Angular 会把这个表达式当做字符串常量看待,并用该字符串来初始化目标属性。 它不会计算这个字符串。
- 不管是插值表达式还是属性绑定,Angular都不会允许带有 script 标签的 HTML 泄漏到浏览器中。
attribute、class 和 style 绑定
attribute 绑定
- 可以通过attribute 绑定来直接设置 attribute 的值。
- 当元素没有属性可绑的时候,就必须使用 attribute 绑定。如: table 中的 colspan/rowspan 等 attribute。 它们是纯粹的 attribute,没有对应的属性可供绑定。
- 插值表达式和属性绑定只能设置属性,不能设置 attribute。
- attribute 绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由attr前缀,一个点 (.) 和 attribute 的名字组成。 可以通过值为字符串的表达式来设置 attribute 的值。
- 这里把
[attr.colspan]
绑定到一个计算值:
<table border=1> <!-- expression calculates colspan=2 --> <tr><td [attr.colspan]="1 + 1">One-Two</td></tr> <!-- ERROR: There is no `colspan` property to set! <tr><td colspan="{{1 + 1}}">Three-Four</td></tr> --> <tr><td>Five</td><td>Six</td></tr> </table>
CSS类绑定
<!-- reset/override all class names with a binding -->
<div class="bad curly special"
[class]="badCurly">Bad curly</div>
说明:当 badCurly 有值时 class 这个 attribute 设置的内容会被完全覆盖。
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
<!-- binding to `class.special` trumps the class attribute -->
<div class="special" [class.special]="!isSpecial">This one is not so special</div>
说明:可以绑定到特定的类名。 当模板表达式的求值结果是真值时,Angular 会添加这个类,反之则移除它。
注:虽然这是切换单一类名的好办法,但我们通常更喜欢使用 NgClass指令 来同时管理多个类名。
样式绑定
<button [style.color] = "isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
根据条件用 “em” 和 “%” 来设置字体大小的单位。
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
注:虽然这是设置单一样式的好办法,但我们通常更喜欢使用 NgStyle指令 来同时设置多个内联样式。
注意,样式属性命名方法可以用中线命名法,像上面的一样 也可以用驼峰式命名法,如fontSize
。
事件绑定
事件绑定语法由等号左侧带圆括号的目标事件和右侧引号中的模板语句组成。
<button (click)="onSave()">Save</button>
目标事件
圆括号中的名称 —— 比如(click)
—— 标记出目标事件。
<button (click)="onSave()">Save</button>
有些人更喜欢带on-前缀的备选形式,称之为规范形式:
<button on-click="onSave()">On Save</button>
元素事件可能是更常见的目标,但 Angular 会先看这个名字是否能匹配上已知指令的事件属性。
<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event">click with myClick</div>
如果这个名字没能匹配到元素事件或已知指令的输出属性,Angular 就会报“未知指令”错误。
$event和事件处理语句
- 绑定会通过名叫
$event
的事件对象传递关于此事件的信息(包括数据值)。 - 事件对象的形态取决于目标事件。如果目标事件是原生 DOM 元素事件, $event就是 DOM事件对象,它有像
target
和target.value
这样的属性。
<input [value]="currentHero.firstName"
(input)="currentHero.firstName=$event.target.value" >
使用 EventEmitter 实现自定义事件
- 通常,指令使用 Angular EventEmitter 来触发自定义事件。
- 指令创建一个EventEmitter实例,并且把它作为属性暴露出来。 指令调用EventEmitter.emit(payload)来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过$event对象来访问载荷。
双向数据绑定
- Angular提供了一种特殊的双向数据绑定语法:
[(x)]
。[(x)]
语法结合了属性绑定的方括号[x]
和事件绑定的圆括号(x)
。 - 双向绑定语法实际上是属性绑定和事件绑定的语法糖。
如:自定义组件SizerComponent
import { Component,EventEmitter,Input,Output } from '@angular/core';
@Component({
selector: 'my-sizer',template: `
<div>
<button (click)="dec()" title="smaller">-</button>
<button (click)="inc()" title="bigger">+</button>
<label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>`
})
export class SizerComponent {
@Input() size: number | string;
@Output() sizeChange = new EventEmitter<number>();
dec() { this.resize(-1); }
inc() { this.resize(+1); }
resize(delta: number) {
this.size = Math.min(40,Math.max(8,+this.size + delta));
this.sizeChange.emit(this.size);
}
}
在父组件AppComponent中可以这么使用:
<my-sizer [(size)]="fontSizePx"></my-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
Angular将SizerComponent的绑定分解成这样:
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
这很容易理解:通过使用父组件AppComponent中的fontSizePx属性初始化SizerComponent组件中的size属性。当SizerComponent组件中的size属性发生变化时,就调用sizeChange事件的emit方法传递改变后的值。$event变量就包含了SizerComponent.sizeChange事件的荷载,即被emi方法传递过来的值。然后被重新赋值给父组件的fontSizePx属性。
注:我们希望能在像和这样的 HTML 元素上使用双向数据绑定。 可惜,原生 HTML 元素不遵循x值和xChange事件的模式。幸运的是,Angular 以 NgModel 指令为桥梁,允许在表单元素上使用双向数据绑定。
使用NgModel进行双向数据绑定
在使用ngModel做双向数据绑定之前,得先导入FormsModule。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,FormsModule
],declarations: [
AppComponent
],bootstrap: [ AppComponent ]
})
export class AppModule { }
[(ngModel)]
内幕
<input [(ngModel)]="currentHero.firstName">
可以通过分别绑定元素的value属性和`input事件来实现同样的效果:
<input [value]="currentHero.firstName"
(input)="currentHero.firstName=$event.target.value" >
ngModel指令通过它自己的ngModel输入属性和ngModelChange输出属性隐藏了这些繁琐的细节。
<input
[ngModel]="currentHero.firstName"
(ngModelChange)="currentHero.firstName=$event">
注:ngModel
数据属性设置元素的 value 属性,ngModelChange
事件属性监听元素 value 的变化。
其他应用,如使用ngModel的展开形式,强制让输入值变成大写形式:
<input
[ngModel]="currentHero.firstName"
(ngModelChange)="setUpperCaseFirstName($event)">
内置指令
NgClass
CSS 类绑定(上述有提到过) 是添加或删除单个类的最佳途径。
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
当想要同时添加或移除多个 CSS 类时,NgClass指令可能是更好的选择。
绑定到NgClass的值时一个 key:value形式的控制对象,这个对象中的每个key都是一个CSS类名,如果它的value为true,这个类就会被加上,否则就会被移除。如:
setClasses() {
let classes = {
saveable: this.canSave,// true
modified: !this.isUnchanged,// false
special: this.isSpecial,// true
};
return classes;
}
使用NgClass属性绑定:
<div [ngClass]="setClasses()">This div is saveable and special</div>
NgStyle
样式绑定(上述有提到过)是设置单一样式值的简单方式。
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
This div is x-large.
</div>
如果要同时设置多个内联样式,NgStyle指令可能是更好的选择。
NgStyle需要绑定到一个 key:value 控制对象。 对象的每个 key 是样式名,它的 value 是能用于这个样式的任何值。如:
setStyles() {
let styles = {
// CSS property names
'font-style': this.canSave ? 'italic' : 'normal',// italic
'font-weight': !this.isUnchanged ? 'bold' : 'normal',// normal
'font-size': this.isSpecial ? '24px' : '8px',// 24px
};
return styles;
}
<div [ngStyle]="setStyles()">
This div is italic,normal weight,and extra large (24px).
</div>
NgIf
通过绑定NgIf
指令到真值表达式,可以把元素子树(元素及其子元素)添加到 DOM 上。绑定到假值表达式将从 DOM 中移除元素子树。
<div *ngIf="currentHero">Hello,{{currentHero.firstName}}</div>
NgSwitch
当需要从一组可能的元素树中根据条件显示一个时,我们就把它绑定到NgSwitch。 Angular 将只把选中的元素树放进 DOM 中。
<span [ngSwitch]="toeChoice">
<span *ngSwitchCase="'Eenie'">Eenie</span>
<span *ngSwitchCase="'Meanie'">Meanie</span>
<span *ngSwitchCase="'Miney'">Miney</span>
<span *ngSwitchCase="'Moe'">Moe</span>
<span *ngSwitchDefault>other</span>
</span>
注:不要在ngSwitch的前面加星号 (),而应该用属性绑定。要把星号 () 放在ngSwitchCase和ngSwitchDefault的前面。
NgFor
使用同一模板渲染多个条目:
<div *ngFor="let hero of heroes">{{hero.fullName}}</div>
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
NgFor 微语法
赋值给*ngFor的字符串不是模板表达式。 它是一个微语法 —— 由 Angular 自己解释的小型语言。在这个例子中,字符串”let hero of heroes”的含义是:取出heroes数组中的每个英雄,把它存入局部变量hero中,并在每次迭代时对模板 HTML 可用。
hero前面的let关键字创建了名叫hero的模板输入变量。
注:模板输入变量和模板引用变量不是一回事!
带索引的 NgFor
ngFor指令支持可选的index,它在迭代过程中会从 0 增长到“数组的长度”。 可以通过模板输入变量来捕获这个 index,并在模板中使用。
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>
NgForTrackBy
trackByHeroes(index: number,hero: Hero) { return hero.id; }
现在,把NgForTrackBy指令设置为那个追踪函数。
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
追踪函数不会阻止所有 DOM 更改。 如果同一个英雄的属性变化了,Angular 就可能不得不更新DOM元素。 但是如果这个属性没有变化 —— 而且大多数时候它们不会变化 —— Angular 就能留下这些 DOM 元素。列表界面就会更加平滑,提供更好的响应。
* 与 <template>
- *是一种语法糖,它让那些需要借助模板来修改 HTML 布局的指令更易于读写。 NgFor、NgIf和NgSwitch都会添加或移除元素子树,这些元素子树被包裹在标签中。
- 我们没有看到
<template>
标签,那是因为这种*前缀语法让我们忽略了这个标签, 而把注意力直接聚焦在所要包含、排除或重复的那些 HTML 元素上。
展开*ngIf
把*
前缀语法展开成 template 语法:
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>
展开的第一步是把ngIf(没有*前缀)和它的内容传给表达式,再赋值给template指令。
<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail>
下一步,也是最后一步,是把 HTML 包裹进标签和[ngIf]属性绑定中:
<template [ngIf]="currentHero">
<hero-detail [hero]="currentHero"></hero-detail>
</template>
展开*ngSwitch
<span [ngSwitch]="toeChoice">
<!-- with *NgSwitch -->
<span *ngSwitchCase="'Eenie'">Eenie</span>
<span *ngSwitchCase="'Meanie'">Meanie</span>
<span *ngSwitchCase="'Miney'">Miney</span>
<span *ngSwitchCase="'Moe'">Moe</span>
<span *ngSwitchDefault>other</span>
<!-- with <template> -->
<template [ngSwitchCase]="'Eenie'"><span>Eenie</span></template>
<template [ngSwitchCase]="'Meanie'"><span>Meanie</span></template>
<template [ngSwitchCase]="'Miney'"><span>Miney</span></template>
<template [ngSwitchCase]="'Moe'"><span>Moe</span></template>
<template ngSwitchDefault><span>other</span></template>
</span>
展开*ngFor
<hero-detail *ngFor="let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detai
<template ngFor let-hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes">
<hero-detail [hero]="hero"></hero-detail>
</template>
模板引用变量
- 模板引用变量是模板中对 DOM 元素或指令的引用。
- 它能在原生 DOM 元素中使用,也能用于 Angular 组件 —— 实际上,它可以和任何自定义 Web 组件协同工作。
- 可以在同一元素、兄弟元素或任何子元素中引用模板引用变量。
- 不要在同一个模版中多次定义相同变量名,否则运行时的值将会不可预测。
- 关于创建和使用模板引用变量的另外两个例子:
<!-- phone refers to the input element; pass its `value` to an event handler -->
<input #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>
<!-- fax refers to the input element; pass its `value` to an event handler -->
<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>
“phone” 的井号 (#) 前缀表示定义了一个phone变量。
注:有些人不喜欢使用#字符,而是使用它的规范形式:ref-前缀。 例如,既能用#phone,也能用ref-phone来定义phone变量。
6. Angular把模板引用变量的值设置为它所在那个元素。
NgForm和模板引用变量
表单,使用模板引用变量的典范——简化过的范例
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input class="form-control" name="name" required [(ngModel)]="currentHero.firstName">
</div>
<button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>
theForm
变量的值是什么?
实际上它是个ngForm,对 Angular 内置指令NgForm的引用。 它包装了原生的HTMLFormElement并赋予它更多超能力,比如跟踪用户输入的有效性。
这解释了该如何通过检查theForm.form.valid来禁用提交按钮,以及如何把一个信息量略大的对象传给父组件的onSubmit方法。
输入输出属性
记住:所有组件皆为指令。(此前,提到过:其实组件也是指令,由于组件的特殊,所以被专门提出来。记住这句话将有利于对后文的理解。)
1. 绑定的目标是在=
左侧的部分,源则是在=
右侧的部分。
2. 绑定的目标是绑定符:[]
、()
或[()]
中的属性或事件名, 源则是引号 (" "
) 中的部分或插值符号 ({{}}
) 中的部分。
3. 源指令中的每个成员都会自动在绑定中可用。 不需要特别做什么,就能在模板表达式或语句中访问指令的成员。(意思就是如:[value]=”hero”,像这样的绑定,对于=
右侧的""
中源,可以使用到指令中的每个成员。(记住:组件也是指令。这里你可以进行类比。))
4. 访问目标指令中的成员则受到限制。 只能绑定到那些显式标记为输入或输出的属性。(那什么是输入或输出属性呢?如何进行显式标记呢?)
声明输入和输出属性
目标属性必须被显式的标记为输入或输出。
如自定义的hero-detail组件
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>
这里的目标属性有hero和deleteRequest。那么既然作为目标属性,那么就要被显式地标记为输入或输出属性。
当我们深入HeroDetailComponent内部时,就会看到这些属性被装饰器标记成了输入和输出属性。
@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
另外,还可以在指令元数据的inputs
或outputs
数组中标记出这些成员。比如这个例子:
@Component({
inputs: ['hero'],outputs: ['deleteRequest'],})
注:既可以通过装饰器,也可以通过元数据数组来指定输入/输出属性。但别同时用!
输入还是输出?
- 输入属性通常接收数据值。输出属性暴露事件生产者,如
EventEmitter
对象。 - 输入和输出这两个词是从目标指令的角度来说的。即输入:向目标指令输入数据;输出:从目标指令流出。
- 从HeroDetailComponent角度来看(
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)"></hero-detail>
),HeroDetailComponent.hero
是个输入属性,因为数据流从模板绑定表达式流入那个属性。HeroDetailComponent.deleteRequest
是个输出属性,因为事件从那个属性流出,流向模板绑定语句中的处理器。
给输入/输出属性起别名
<div (myClick)="clickMessage=$event">click with myClick</div>
而实际在指令类中为clicks属性,只是指定了外部对应的该属性的别名:
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
也可在inputs和outputs数组中为属性指定别名。 可以写一个冒号 (:) 分隔的字符串,左侧是指令中的属性名,右侧则是公开的别名。
@Directive({
outputs: ['clicks:myClick'] // propertyName:alias
})
模板表达式操作符
模板表达式语言使用了 JavaScript 语法的子集,并补充了几个用于特定场景的特殊操作符。
管道操作符 ( | )
在绑定之前,表达式的结果可能需要一些转换。例如,可能希望把数字显示成金额、强制文本变成大写,或者过滤列表以及进行排序。
Angular 管道对像这样的小型转换来说是个明智的选择。 管道是一个简单的函数,它接受一个输入值,并返回转换结果。 它们很容易用于模板表达式中,只要使用管道操作符 (|) 就行了。
<div>Title through uppercase pipe: {{title | uppercase}}</div> <!-- Pipe chaining: convert title to uppercase,then to lowercase --> <div> Title through a pipe chain: {{title | uppercase | lowercase}} </div> <!-- pipe with configuration argument => "February 25,1970" --> <div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>
json管道对调试绑定特别有用:
<div>{{currentHero | json}}</div>
安全导航操作符 ( ?. ) 和空属性路径
Angular 的安全导航操作符 (?.) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。 下例中,当currentHero为空时,保护视图渲染器,让它免于失败。
The current hero's name is {{currentHero?.firstName}}
以下类似的方法也可以实现同样的功能:
通过写NgIf代码来解决这个问题:
<!--No hero,div not displayed,no error --> <div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
或者可以尝试通过&&来把属性路径的各部分串起来,让它在遇到第一个空值的时候,就返回空。
The null hero's name is {{nullHero && nullHero.firstName}}
这些方法都有价值,但是会显得笨重,特别是当这个属性路径非常长的时候。 想象一下在一个很长的属性路径(如a.b.c.d)中对空值提供保护。
Angular 安全导航操作符 (?.) 是在属性路径中保护空值的更加流畅、便利的方式。 表达式会在它遇到第一个空值的时候跳出。 显示是空的,但应用正常工作,而没有发生错误。
<!-- No hero,no problem! --> The null hero's name is {{nullHero?.firstName}}
在像a?.b?.c?.d
这样的长属性路径中,它工作得很完美。