1,
模型-视图-控制器 (MVC) 或模型-视图-视图模型 (MVVM)
在 Angular 中,组件扮演着控制器或视图模型的角色,模板则扮演视图的角色
HTML 是 Angular 模板的语言。几乎所有的 HTML 语法都是有效的模板语法。 但值得注意的例外是 <script>
元素,它被禁用了,以阻止脚本注入攻击的风险。(实际上,<script>
只是被忽略了。)
有些合法的 HTML 被用在模板中是没有意义的。<html>
、<body>
和 <base>
元素这个舞台上中并没有扮演有用的角色。剩下的所有元素基本上就都一样用了。
可以通过组件和指令来扩展模板中的 HTML 词汇。它们看上去就是新元素和属性。接下来将学习如何通过数据绑定来动态获取/设置 DOM(文档对象模型)的值。
{{...}}
所谓 "插值" 是指将表达式嵌入到标记文本中。 默认情况下,插值表达式会用双花括号 {{
和 }}
作为分隔符。
在下面的代码片段中,{{ currentCustomer }}
就是插值表达式的例子。
<h3>Current customer: {{ currentCustomer }}</h3>
在括号之间的“素材”,通常是组件属性的名字。Angular 会用组件中相应属性的字符串值
一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串
插值其实是一个特殊语法,Angular 会把它转换为属性绑定。
如果你想用别的分隔符来代替 {{
和 }}
,也可以通过 Component
元数据中的 interpolation 选项来配置插值分隔
模板表达式会产生一个值,并出现在双花括号 {{ }}
中。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。
典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。 在下面的代码片段中,双花括号中的 recommended
和引号中的 itemImageUrl2
所引用的都是 AppComponent
中的属性。
<h4>{{recommended}}</h4>
<img [src]="itemImageUrl2">
表达式的上下文可以包括组件之外的对象。 比如模板输入变量 (let customer
)和模板引用变量(#customerInput
)就是备选的上下文对象之一。
<ul>
<li *ngFor="let customer of customers">{{customer.name}}</li>
</ul>
<label>Type something:
<input #customerInput>{{customerInput.value}}
</label>
表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果你要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。
在 {{customer.name}}
表达式中的 customer
实际引用的是模板变量,而不是组件的属性。
模板表达式不能引用全局命名空间中的任何东西,比如 window
或 document
。它们也不能调用 console.log
或 Math.max
。 它们只能引用表达式上下文中的成员。
模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。 模板语句将在事件绑定一节看到,它出现在 =
号右侧的引号中,就像这样:(event)="statement"
。
<button (click)="deleteHero()">Delete hero</button>
模板语句有副作用。 这是事件处理的关键。因为你要根据用户的输入更新应用状态。
响应事件是 Angular 中“单向数据流”的另一面。 在一次事件循环中,可以随意改变任何地方的任何东西。
和模板表达式一样,模板语句使用的语言也像 JavaScript。 模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值 (=
) 和表达式链 (;
和 ,
)。
和表达式中一样,语句只能引用语句上下文中 —— 通常是正在绑定事件的那个组件实例。
典型的语句上下文就是当前组件的实例。 (click)="deleteHero()"
中的 deleteHero 就是这个数据绑定组件上的一个方法。
语句上下文可以引用模板自身上下文中的属性。 在下面的例子中,就把模板的 $event
对象、模板输入变量 (let hero
)和模板引用变量 (#heroForm
)传给了组件中的一个事件处理器方法。
<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
模板上下文中的变量名的优先级高于组件上下文中的变量名。在上面的 deleteHero(hero)
中,hero
是一个模板输入变量,而不是组件中的 hero
属性。
模板语句不能引用全局命名空间的任何东西。比如不能引用 window
或 document
,也不能调用 console.log
或 Math.max
绑定的类型可以根据数据流的方向分成三类: 从数据源到视图、从视图到数据源以及双向的从视图到数据源再到视图
单向 | content_copy | 插值 | |
从视图到数据源的单向绑定 | content_copy | 事件 | |
双向 | content_copy
|
双向 |
要想理解 Angular 绑定如何工作,重点是搞清 HTML attribute 和 DOM property 之间的区别。
attribute 是由 HTML 定义的。property 是由 DOM (Document Object Model) 定义的。
少量 HTML attribute 和 property 之间有着 1:1 的映射,如 id
。
有些 HTML attribute 没有对应的 property,如 colspan
。
有些 DOM property 没有对应的 attribute,如 textContent
。
大量 HTML attribute 看起来映射到了 property…… 但却不像你想的那样!
最后一类尤其让人困惑…… 除非你能理解这个普遍原则:
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 的 value
这个 attribute 指定了初始值;DOM 的 value
这个 property 是当前值。
disabled
这个 attribute 是另一种特例。按钮的 disabled
这个 property 是 false
,因为默认情况下按钮是可用的。 当你添加 disabled
这个 attribute 时,只要它出现了按钮的 disabled
这个 property 就初始化为 true
,于是按钮就被禁用了。
添加或删除 disabled
这个 attribute 会禁用或启用这个按钮。但 attribute 的值无关紧要,这就是你为什么没法通过 <button disabled="false">仍被禁用</button>
这种写法来启用按钮。
设置按钮的 disabled
这个 property(如,通过 Angular 绑定)可以禁用或启用这个按钮。 这就是 property 的价值。
就算名字相同,HTML attribute 和 DOM property 也不是同一样东西。
这句话值得再强调一次: 模板绑定是通过 property 和事件来工作的,而不是 attribute
在 Angular 的世界中,attribute 唯一的作用是用来初始化元素和指令的状态。 当进行数据绑定时,只是在与元素和指令的 property 和事件打交道,而 attribute 就完全靠边站了。
数据绑定的目标是 DOM 中的某些东西。 这个目标可能是(元素 | 组件 | 指令的)property、(元素 | 组件 | 指令的)事件,或(极少数情况下) attribute 名。 下面是的汇总表:
绑定类型 | 目标 | 范例 |
---|---|---|
属性 | 元素的 property | src/app/app.component.html content_copy |
事件 | 元素的事件 | src/app/app.component.html content_copy |
双向 | 事件与 property | src/app/app.component.html content_copy |
Attribute | attribute(例外情况) | src/app/app.component.html content_copy |
CSS 类 |
| src/app/app.component.html content_copy |
样式 |
| src/app/app.component.html content_copy |
当要把视图元素的属性 (property) 设置为模板表达式时,就要写模板的属性 (property) 绑定。
最常用的属性绑定是把元素属性设置为组件属性的值。 下面这个例子中,image 元素的 src
属性会被绑定到组件的 heroImageUrl
属性上:
<img [src]="heroImageUrl">
有些人喜欢用 bind-
前缀的可选形式,并称之为规范形式:
content_copy<img bind-src="heroImageUrl">
目标的名字总是 property 的名字。即使它看起来和别的名字一样。 看到 src
时,可能会把它当做 attribute。不!它不是!它是 image 元素的 property 名,类似于对象属性
另一个例子是当组件说它 isUnchanged
(未改变)时禁用按钮:
<button [disabled]="isUnchanged">Cancel is disabled</button>
人们经常把属性绑定描述成单向数据绑定,因为值的流动是单向的,从组件的数据属性流动到目标元素的属性。
不能使用属性绑定来从目标元素拉取值,也不能绑定到目标元素的属性来读取它。只能设置它。
方括号告诉 Angular 要计算模板表达式。 如果忘了加方括号,Angular 会把这个表达式当做字符串常量看待,并用该字符串来初始化目标属性。 它不会计算这个字符串。
不要出现这样的失误:
content_copy<!-- ERROR: HeroDetailComponent.hero expects a
Hero object, not the string "currentHero" -->
<app-hero-detail hero="currentHero"></app-hero-detail>
你通常得在插值和属性绑定之间做出选择。 下列这几对绑定做的事情完全相同:
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
在多数情况下,插值是更方便的备选项。
当要渲染的数据类型是字符串时,没有技术上的理由证明哪种形式更好。 你倾向于可读性,所以倾向于插值。 建议建立代码风格规则,选择一种形式, 这样,既遵循了规则,又能让手头的任务做起来更自然。
但数据类型不是字符串时,就必须使用属性绑定了。
可以通过attribute 绑定来直接设置 attribute 的值。
这是“绑定到目标属性 (property)”这条规则中唯一的例外。这是唯一的能创建和设置 attribute 的绑定形式。
本章中,通篇都在说通过属性绑定来设置元素的属性总是好于用字符串设置 attribute。为什么 Angular 还提供了 attribute 绑定呢?
因为当元素没有属性可绑的时候,就必须使用 attribute 绑定。
考虑 ARIA, SVG 和 table 中的 colspan/rowspan 等 attribute。 它们是纯粹的 attribute,没有对应的属性可供绑定。
如果想写出类似下面这样的东西,就会暴露出痛点了:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
会得到这个错误:
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property
正如提示中所说,<td>
元素没有 colspan
属性。 但是插值和属性绑定只能设置属性,不能设置 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 类绑定,可以从元素的 class
attribute 上添加和移除 CSS 类名。
CSS 类绑定绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由class
前缀,一个点 (.
)和 CSS 类的名字组成, 其中后两部分是可选的。形如:[class.class-name]
。
下列例子示范了如何通过 CSS 类绑定来添加和移除应用的 "special" 类。不用绑定直接设置 attribute 时是这样的:
<!-- standard class attribute setting -->
<div class="bad curly special">Bad curly special</div>
可以把它改写为绑定到所需 CSS 类名的绑定;这是一个或者全有或者全无的替换型绑定。 (译注:即当 badCurly 有值时 class 这个 attribute 设置的内容会被完全覆盖)
<!-- reset/override all class names with a binding -->
<div class="bad curly special"
[class]="badCurly">Bad curly</div>
最后,可以绑定到特定的类名。 当模板表达式的求值结果是真值时,Angular 会添加这个类,反之则移除它
<!-- 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>
通过样式绑定,可以设置内联样式。
样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由style
前缀,一个点 (.
)和 CSS 样式的属性名组成。 形如:[style.style-property]
。
content_copy<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>
(event)
事件绑定允许你侦听某些事件,比如按键、鼠标移动、点击和触屏
Angular 的事件绑定语法由等号左侧带圆括号的目标事件和右侧引号中的模板语句组成。 下面事件绑定监听按钮的点击事件。每当点击发生时,都会调用组件的 onSave()
方法。
如前所述,其目标就是此按钮的单击事件。
<button (click)="onSave($event)">Save</button>
有些人更喜欢带 on-
前缀的备选形式,称之为规范形式:
<button on-click="onSave($event)">on-click Save</button>
在事件绑定中,Angular 会为目标事件设置事件处理器。
当事件发生时,这个处理器会执行模板语句。 典型的模板语句通常涉及到响应事件执行动作的接收器,例如从 HTML 控件中取得值,并存入模型。
绑定会通过名叫 $event
的事件对象传递关于此事件的信息(包括数据值)。
事件对象的形态取决于目标事件。如果目标事件是原生 DOM 元素事件, $event
就是 DOM 事件对象,它有像 target
和 target.value
这样的属性。
<input [value]="currentItem.name"
(input)="currentItem.name=$event.target.value" >
without NgModel
上面的代码在把输入框的 value
属性绑定到 name
属性。 要监听对值的修改,代码绑定到输入框的 input
事件。 当用户造成更改时,input
事件被触发,并在包含了 DOM 事件对象 ($event
) 的上下文中执行这条语句。
要更新 name
属性,就要通过路径 $event.target.value
来获取更改后的值。
EventEmitter
实现自定义事件通常,指令使用 Angular EventEmitter 来触发自定义事件。 指令创建一个 EventEmitter
实例,并且把它作为属性暴露出来。 指令调用 EventEmitter.emit(payload)
来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过 $event
对象来访问载荷。
假设 HeroDetailComponent
用于显示英雄的信息,并响应用户的动作。 虽然 HeroDetailComponent
包含删除按钮,但它自己并不知道该如何删除这个英雄。 最好的做法是触发事件来报告“删除用户”的请求。
下面的代码节选自 HeroDetailComponent
:
src/app/item-detail/item-detail.component.html (template)
<img src="{{itemImageUrl}}" [style.display]="displayNone">
<span [style.text-decoration]="lineThrough">{{ item.name }}
</span>
<button (click)="delete()">Delete</button>
src/app/item-detail/item-detail.component.ts (deleteRequest)
// This component makes a request but it can't actually delete a hero.
@Output() deleteRequest = new EventEmitter<Item>();
delete() {
this.deleteRequest.emit(this.item);
this.displayNone = this.displayNone ? '' : 'none';
this.lineThrough = this.lineThrough ? '' : 'line-through';
}
组件定义了 deleteRequest
属性,它是 EventEmitter
实例。 当用户点击删除时,组件会调用 delete()
方法,让 EventEmitter
发出一个 Hero
对象。
现在,假设有个宿主的父组件,它绑定了 HeroDetailComponent
的 deleteRequest
事件。
content_copy<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>
当 deleteRequest
事件触发时,Angular 调用父组件的 deleteHero
方法, 在 $event
变量中传入要删除的英雄(来自 HeroDetail
)。
你经常需要显示数据属性,并在用户作出更改时更新该属性。
在元素层面上,既要设置元素属性,又要监听元素事件变化。
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-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);
}
}
size
的初始值是一个输入值,来自属性绑定。(译注:注意 size
前面的 @Input
) 点击按钮,在最小/最大值范围限制内增加或者减少 size
。 然后用调整后的 size
触发 sizeChange
事件。
<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
$event
变量包含了 SizerComponent.sizeChange
事件的荷载。 当用户点击按钮时,Angular 将 $event
赋值给 AppComponent.fontSizePx
。
显然,比起单独绑定属性和事件,双向数据绑定语法显得非常方便。
如果能在像 <input>
和 <select>
这样的 HTML 元素上使用双向数据绑定就更好了。 可惜,原生 HTML 元素不遵循 x
值和 xChange
事件的模式。
幸运的是,Angular 以 NgModel 指令为桥梁,允许在表单元素上使用双向数据绑定。
属性型指令会监听和修改其它 HTML 元素或组件的行为、元素属性(Attribute)、DOM 属性(Property)。 它们通常会作为 HTML 属性的名称而应用在元素上。
更多的细节参见属性型指令一章。 很多 NgModules,比如RouterModule
和FormsModule
都定义了自己的属性型指令。 本节将会介绍几个最常用的属性型指令:
你经常用动态添加或删除 CSS 类的方式来控制元素如何显示。 通过绑定到 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
,这个类就会被加上,否则就会被移除。
组件方法 setCurrentClasses
可以把组件的属性 currentClasses
设置为一个对象,它将会根据三个其它组件的状态为 true
或 false
而添加或移除三个类。
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
把 NgClass
属性绑定到 currentClasses
,根据它来设置此元素的 CSS 类:
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
你既可以在初始化时调用 setCurrentClasses()
,也可以在所依赖的属性变化时调用。
你可以根据组件的状态动态设置内联样式。 NgStyle
绑定可以同时设置多个内联样式。
样式绑定是设置单一样式值的简单方式。
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
This div is x-large or smaller.
</div>
如果要同时设置多个内联样式,NgStyle
指令可能是更好的选择。
NgStyle
需要绑定到一个 key:value 控制对象。 对象的每个 key 是样式名,它的 value 是能用于这个样式的任何值。
来看看组件的 setCurrentStyles
方法,它会根据另外三个属性的状态把组件的 currentStyles
属性设置为一个定义了三个样式的对象:
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
把 ngStyle
属性绑定到 currentStyles
,来根据它设置此元素的样式:
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
你既可以在初始化时调用 setCurrentStyles()
,也可以在所依赖的属性变化时调用
<input [(ngModel)]="currentHero.name">
<input
[ngModel]="currentHero.name"
(ngModelChange)="currentHero.name=$event">
ngModel
输入属性会设置该元素的值,并通过 ngModelChange
的输出属性来监听元素值的变化。
[(ngModel)]
语法只能设置数据绑定属性。 如果要做更多或者做点不一样的事,也可以写它的展开形式。
<input
[ngModel]="currentHero.name"
(ngModelChange)="setUppercaseName($event)">
结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,这通常是通过添加、移除和操纵它们所附加到的宿主元素来实现的。
本节是对常见结构型指令的简介:
隐藏子树和用 NgIf
排除子树是截然不同的。
当隐藏子树时,它仍然留在 DOM 中。 子树中的组件及其状态仍然保留着。 即使对于不可见属性,Angular 也会继续检查变更。 子树可能占用相当可观的内存和运算资源。
当 NgIf
为 false
时,Angular 从 DOM 中物理地移除了这个元素子树。 它销毁了子树中的组件及其状态,也潜在释放了可观的资源,最终让用户体验到更好的性能。
显示/隐藏的技术对于只有少量子元素的元素是很好用的,但要当心别试图隐藏大型组件树。相比之下,NgIf
则是个更安全的选择。
带索引的 *ngFor
NgFor
指令上下文中的 index
属性返回一个从零开始的索引,表示当前条目在迭代中的顺序。 你可以通过模板输入变量捕获这个 index
值,并把它用在模板中。
下面这个例子把 index
捕获到了 i
变量中,并且把它显示在英雄名字的前面。
content_copy<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
他们中的绝大多数(如果不是所有的话)都是以前显示过的英雄。你知道这一点,是因为每个英雄的 id
没有变化。 但在 Angular 看来,它只是一个由新的对象引用构成的新列表, 它没有选择,只能清理旧列表、舍弃那些 DOM 元素,并且用新的 DOM 元素来重建一个新列表。
如果给它指定一个 trackBy
,Angular 就可以避免这种折腾。 往组件中添加一个方法,它会返回 NgFor
应该追踪的值。 在这里,这个值就是英雄的 id
。
content_copytrackByHeroes(index: number, hero: Hero): number { return hero.id; }
在微语法中,把 trackBy
设置为该方法。
content_copy<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
({{hero.id}}) {{hero.name}}
</div>
这里展示了 trackBy
的效果。 "Reset heroes"会创建一个具有相同 hero.id
的新英雄。 "Change ids"则会创建一个具有新 hero.id
的新英雄。
如果没有 trackBy
,这些按钮都会触发完全的 DOM 元素替换。
有了 trackBy
,则只有修改了 id
的按钮才会触发元素替换。
NgSwitch
指令NgSwitch
指令类似于 JavaScript 的 switch
语句。 它可以从多个可能的元素中根据switch 条件来显示某一个。 Angular 只会把选中的元素放进 DOM 中。
NgSwitch
实际上包括三个相互协作的指令:NgSwitch
、NgSwitchCase
和 NgSwitchDefault
,例子如下:
<div [ngSwitch]="currentHero.emotion">
<app-happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></app-happy-hero>
<app-sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></app-sad-hero>
<app-confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></app-confused-hero>
<app-unknown-hero *ngSwitchDefault [hero]="currentHero"></app-unknown-hero>
</div>
NgSwitch
是主控指令,要把它绑定到一个返回候选值的表达式。 本例子中的 emotion
是个字符串,但实际上这个候选值可以是任意类型。
绑定到 [ngSwitch]
。如果试图用 *ngSwitch
的形式使用它就会报错,这是因为 NgSwitch
是一个属性型指令,而不是结构型指令。 它要修改的是所在元素的行为,而不会直接接触 DOM 结构。
绑定到 *ngSwitchCase
和 *ngSwitchDefault
NgSwitchCase
和 NgSwitchDefault
指令都是结构型指令,因为它们会从 DOM 中添加或移除元素。
NgSwitchCase
会在它绑定到的值等于候选值时,把它所在的元素加入到 DOM 中。
NgSwitchDefault
会在没有任何一个 NgSwitchCase
被选中时把它所在的元素加入 DOM 中。
模板引用变量通常用来引用模板中的某个 DOM 元素,它还可以引用 Angular 组件或指令或Web Component。
使用井号 (#) 来声明引用变量。 #phone
的意思就是声明一个名叫 phone
的变量来引用 <input>
元素。
<input #phone placeholder="phone number">
你可以在模板中的任何地方引用模板引用变量。 比如声明在 <input>
上的 phone
变量就是在模板另一侧的 <button>
上使用的。
<input #phone placeholder="phone number">
<!-- lots of other elements -->
<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>
在本章的例子中,绑定到 HeroDetailComponent
不会失败,这是因为这些要进行数据绑定的属性都带有 @Input()
和 @Output()
装饰器。
@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
另外,还可以在指令元数据的 inputs
或 outputs
数组中标记出这些成员。比如这个例子:
@Component({
inputs: ['hero'],
outputs: ['deleteRequest'],
})
输入属性通常接收数据值。 输出属性暴露事件生产者,如 EventEmitter
对象。
输入和输出这两个词是从目标指令的角度来说的。
从 HeroDetailComponent
角度来看,HeroDetailComponent.hero
是个输入属性, 因为数据流从模板绑定表达式流入那个属性。
从 HeroDetailComponent
角度来看,HeroDetailComponent.deleteRequest
是个输出属性, 因为事件从那个属性流出,流向模板绑定语句中的处理器。
管道操作符会把它左侧的表达式结果传给它右侧的管道函数。
<div>Title through uppercase pipe: {{title | uppercase}}</div>
还可以通过多个管道串联表达式:
content_copy<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
<div>
Title through a pipe chain:
{{title | uppercase | lowercase}}
</div>
安全导航操作符 ( ?. ) 和空属性路径
gular 的安全导航操作符 (?.
) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。 下例中,当 currentHero
为空时,保护视图渲染器,让它免于失败。
Angular 安全导航操作符 (?.
) 是在属性路径中保护空值的更加流畅、便利的方式。 表达式会在它遇到第一个空值的时候跳出。 显示是空的,但应用正常工作,而没有发生错误。
The current hero's name is {{currentHero?.name}}
如果类型检查器在运行期间无法确定一个变量是 null 或 undefined,那么它也会抛出一个错误。 你自己可能知道它不会为空,但类型检查器不知道。 所以你要告诉类型检查器,它不会为空,这时就要用到非空断言操作符。
Angular 模板中的**非空断言操作符(!
)也是同样的用途。
例如,在用*ngIf来检查过 hero
是已定义的之后,就可以断言 hero
属性一定是已定义的。
content_copy<!--No hero, no text -->
<div *ngIf="hero">
The hero's name is {{hero!.name}}
</div>
与安全导航操作符不同的是,非空断言操作符不会防止出现 null 或 undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检测"。
$any()
有时候,绑定表达式可能会在 AOT 编译时报类型错误,并且它不能或很难指定类型。要消除这种报错,你可以使用 $any()
转换函数来把表达式转换成 any
类型,范例如下:
content_copy<p>The item's undeclared best by date is: {{$any(item).bestByDate}}</p>
当 Angular 编译器把模板转换成 TypeScript 代码时,$any
表达式可以防止 TypeScript 编译器在进行类型检查时报错说 bestByDate
不是 item
对象的成员。
$any()
转换函数可以和 this
联合使用,以便访问组件中未声明过的成员。
src/app/app.component.html
content_copy<p>The item's undeclared best by date is: {{$any(this).bestByDate}}</p>
$any()
转换函数可以用在绑定表达式中任何可以进行方法调用的地方。