阅读(2342) 赞(3)

Angular9 模板语法

2020-06-29 17:48:37 更新

这是一篇关于 Angular 模板语言的技术大全。 在本篇中解释了模板语言的基本原理,并描述了你将在本教程的学习中遇到的大部分语法。

Angular 应用管理着用户之所见和所为,并通过 Component 类的实例(组件)和面向用户的模板交互来实现这一点。

从使用模型-视图-控制器 (MVC) 或模型-视图-视图模型 (MVVM) 的经验中,很多开发人员都熟悉了组件和模板这两个概念。 在 Angular 中,组件扮演着控制器或视图模型的角色,模板则扮演视图的角色。

这是一篇关于 Angular 模板语言的技术大全。 它解释了模板语言的基本原理,并描述了你将在文档中其它地方遇到的大部分语法。

模板中的 HTML

HTML 是 Angular 模板的语言。几乎所有的 HTML 语法都是有效的模板语法。 但值得注意的例外是 <script>元素,它被禁用了,以阻止脚本注入攻击的风险。(实际上,<script> 只是被忽略了。)

有些合法的 HTML 被用在模板中是没有意义的。比如<html>、<body> 和 <base> 这几个元素在这其中并没有扮演有用的角色。剩下的所有元素基本上就都有其所用了。

可以通过组件和指令来扩展模板中的 HTML 词汇。它们看上去就是新元素和属性。接下来将学习如何通过数据绑定来动态获取/设置 DOM(文档对象模型)的值。

首先看看数据绑定的第一种形式 —— 插值,它展示了模板的 HTML 可以有多丰富。

插值与模板表达式

插值能让你把计算后的字符串合并到 HTML 元素标签之间和属性赋值语句内的文本中。模板表达式则是用来供你求出这些字符串的。

插值 {{...}}

所谓 "插值" 是指将表达式嵌入到标记文本中。 默认情况下,插值会用双花括号 {{ }} 作为分隔符。

在下面的代码片段中,{{ currentCustomer }} 就是插值的例子。

Path:"src/app/app.component.html"

<h3>Current customer: {{ currentCustomer }}</h3>

花括号之间的文本currentCustomer通常是组件属性的名字。Angular 会把这个名字替换为响应组件属性的字符串值。

Path:"src/app/app.component.html"

<p>{{title}}</p>
<div><img src="{{itemImageUrl}}"></div>

在上面的示例中,Angular 计算 title 和 itemImageUrl 属性并填充空白,首先显示一些标题文本,然后显示图像。

一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串。 下列插值通过把括号中的两个数字相加说明了这一点:

Path:"src/app/app.component.html"

<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}.</p>

这个表达式可以调用宿主组件的方法,就像下面用的 getVal():

Path:"src/app/app.component.html"

<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>

Angular 对所有双花括号中的表达式求值,把求值的结果转换成字符串,并把它们跟相邻的字符串字面量连接起来。最后,把这个组合出来的插值结果赋给元素或指令的属性。

你看上去似乎正在将结果插入元素标签之间,并将其赋值给属性。 但实际上,插值是一种特殊语法,Angular 会将其转换为属性绑定。

注:
- 如果你想用别的分隔符来代替 {{ }},也可以通过 Component 元数据中的 interpolation 选项来配置插值分隔符。

模板表达式

模板表达式会产生一个值,并出现在双花括号 {{ }} 中。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。

{{1 + 1}} 中所包含的模板表达式是 1 + 1。 在属性绑定中会再次看到模板表达式,它出现在 = 右侧的引号中,就像这样:[property]="expression"。

在语法上,模板表达式与 JavaScript 很像。很多 JavaScript 表达式都是合法的模板表达式,但也有一些例外。

你不能使用那些具有或可能引发副作用的 JavaScript 表达式,包括:

  • 赋值 (=, +=, -=, ...)。

  • new、typeof、instanceof 等运算符。

  • 使用 ; 或 , 串联起来的表达式。

  • 自增和自减运算符:++ 和 --。

  • 一些 ES2015+ 版本的运算符。

和 JavaScript 语法的其它显著差异包括:

  • 不支持位运算,比如 | 和 &。

  • 新的模板表达式运算符,例如 |,?. 和 !。

表达式上下文

典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。 在下面的代码片段中,双花括号中的 recommended 和引号中的 itemImageUrl2 所引用的都是 AppComponent 中的属性。

Path:"src/app/app.component.html"

<h4>{{recommended}}</h4>
<img [src]="itemImageUrl2">

表达式也可以引用模板中的上下文属性,例如模板输入变量,

let customer,或模板引用变量 #customerInput。

Path:"src/app/app.component.html (template input variable)"

<ul>
  <li *ngFor="let customer of customers">{{customer.name}}</li>
</ul>

Path:"src/app/app.component.html (template reference variable)"

<label>Type something:
  <input #customerInput>{{customerInput.value}}
</label>

表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果你要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。

上一个例子中就体现了这种命名冲突。组件具有一个名叫 customer 的属性,而 *ngFor 声明了一个也叫 customer 的模板变量。

注:
- 在 {{customer.name}} 表达式中的 customer 实际引用的是模板变量,而不是组件的属性。

  • 模板表达式不能引用全局命名空间中的任何东西,比如 window 或 document。它们也不能调用 console.log 或 Math.max。 它们只能引用表达式上下文中的成员。

表达式使用指南

当使用模板表达式时,请遵循下列要素:

  1. 非常简单

虽然也可以写复杂的模板表达式,不过最好避免那样做。

属性名或方法调用应该是常态,但偶然使用逻辑取反 ! 也是可以的。 其它情况下,应该把应用程序和业务逻辑限制在组件中,这样它才能更容易开发和测试。

  1. 执行迅速

Angular 会在每个变更检测周期后执行模板表达式。 变更检测周期会被多种异步活动触发,比如 Promise 解析、HTTP 结果、定时器时间、按键或鼠标移动。

表达式应该快速结束,否则用户就会感到拖沓,特别是在较慢的设备上。 当计算代价较高时,应该考虑缓存那些从其它值计算得出的值。

  1. 没有可见的副作用

模板表达式除了目标属性的值以外,不应该改变应用的任何状态。

这条规则是 Angular “单向数据流”策略的基础。 永远不用担心读取组件值可能改变另外的显示值。 在一次单独的渲染过程中,视图应该总是稳定的。

幂等的表达式是最理想的,因为它没有副作用,并且可以提高 Angular 的变更检测性能。 用 Angular 术语来说,幂等表达式总会返回完全相同的东西,除非其依赖值之一发生了变化。

在单独的一次事件循环中,被依赖的值不应该改变。 如果幂等的表达式返回一个字符串或数字,连续调用它两次,也应该返回相同的字符串或数字。 如果幂等的表达式返回一个对象(包括 Date 或 Array),连续调用它两次,也应该返回同一个对象的引用。

注:
- 对于 *ngFor,这种行为有一个例外。*ngFor 具有 trackBy 功能,在迭代对象时它可以处理对象的相等性。详情参见 带 trackBy 的 *ngFor。

模板语句

模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。 模板语句将在事件绑定一节看到,它出现在 = 号右侧的引号中,就像这样:(event)="statement"。

Path:"src/app/app.component.html"

<button (click)="deleteHero()">Delete hero</button>

模板语句有副作用。 这是事件处理的关键。因为你要根据用户的输入更新应用状态。

响应事件是 Angular 中“单向数据流”的另一面。 在一次事件循环中,可以随意改变任何地方的任何东西。

和模板表达式一样,模板语句使用的语言也像 JavaScript。 模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值 (=) 和表达式链 (;)。

然而,某些 JavaScript 语法和模板表达式语法仍然是不允许的:

  • new 运算符

自增和自减运算符:++ 和 --

操作并赋值,例如 += 和 -=

位运算符,例如 | 和 &

管道运算符

语句上下文

和表达式中一样,语句只能引用语句上下文中 —— 通常是正在绑定事件的那个组件实例。

典型的语句上下文就是当前组件的实例。 (click)="deleteHero()" 中的 deleteHero 就是这个数据绑定组件上的一个方法。

Path:"src/app/app.component.html"

<button (click)="deleteHero()">Delete hero</button>

语句上下文可以引用模板自身上下文中的属性。 在下面的例子中,就把模板的 $event 对象、模板输入变量 (let hero)和模板引用变量 (#heroForm)传给了组件中的一个事件处理器方法。

Path:"src/app/app.component.html"

<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。

和表达式一样,避免写复杂的模板语句。 常规是函数调用或者属性赋值。

绑定语法:概览

数据绑定是一种机制,用来协调用户可见的内容,特别是应用数据的值。 虽然也可以手动从 HTML 中推送或拉取这些值,但是如果将这些任务转交给绑定框架,应用就会更易于编写、阅读和维护。 你只需声明数据源和目标 HTML 元素之间的绑定关系就可以了,框架会完成其余的工作。

Angular 提供了多种数据绑定方式。绑定类型可以分为三类,按数据流的方向分为:

  1. 单向:从数据源到视图

  • 绑定类型:插值、属性、Attribute、CSS类、样式。

  • 语法:

    {{expression}}
    [target]="expression"
    bind-target="expression"

  1. 单向:从视图到数据源

  • 绑定类型:事件

  • 语法:

    (target)="statement"
    on-target="statement"

  • 双向:视图到数据源到视图

  • 绑定类型:双向

  • 语法:

    [(target)]="expression"
    bindon-target="expression"

除插值以外的其它绑定类型在等号的左侧都有一个“目标名称”,由绑定符 [] 或 () 包起来, 或者带有前缀:bind-,on-,bindon-。

绑定的“目标”是绑定符内部的属性或事件:[]、() 或 [()]。

在绑定时可以使用来源指令的每个公共成员。 你无需进行任何特殊操作即可在模板表达式或语句内访问指令的成员。

数据绑定与 HTML

在正常的 HTML 开发过程中,你使用 HTML 元素来创建视觉结构, 通过把字符串常量设置到元素的 attribute 来修改那些元素。

<div class="special">Plain old HTML</div>


<img src="images/item.png">
<button disabled>Save</button>

使用数据绑定,你可以控制按钮状态等各个方面:

Path:"src/app/app.component.html"

<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>

注:
- 这里绑定到的是按钮的 DOM 元素的 disabled 这个 Property,而不是 Attribute。

  • 这是数据绑定的通用规则。数据绑定使用 DOM 元素、组件和指令的 Property,而不是 HTML 的 Attribute。

HTML attribute 与 DOM property 的对比

理解 HTML 属性和 DOM 属性之间的区别,是了解 Angular 绑定如何工作的关键。Attribute 是由 HTML 定义的。Property 是从 DOM(文档对象模型)节点访问的。

  • 一些 HTML Attribute 可以 1:1 映射到 Property;例如,“ id”。

  • 某些 HTML Attribute 没有相应的 Property。例如,aria-*。

  • 某些 DOM Property 没有相应的 Attribute。例如,textContent。

重要的是要记住,HTML Attribute 和 DOM Property 是不同的,就算它们具有相同的名称也是如此。 在 Angular 中,HTML Attribute 的唯一作用是初始化元素和指令的状态。

模板绑定使用的是 Property 和事件,而不是 Attribute。

编写数据绑定时,你只是在和目标对象的 DOM Property 和事件打交道。

注:
- 该通用规则可以帮助你建立 HTML Attribute 和 DOM Property 的思维模型: 属性负责初始化 DOM 属性,然后完工。Property 值可以改变;Attribute 值则不能。

  • 此规则有一个例外。 可以通过 setAttribute() 来更改 Attribute,接着它会重新初始化相应的 DOM 属性。

示例 1:<input>

当浏览器渲染 <input type="text" value="Sarah"> 时,它会创建一个对应的 DOM 节点,其 value Property 已初始化为 “Sarah”。

<input type="text" value="Sarah">

当用户在 <input> 中输入 Sally 时,DOM 元素的 value Property 将变为 Sally。 但是,如果使用 input.getAttribute('value') 查看 HTML 的 Attribute value,则可以看到该 attribute 保持不变 —— 它返回了 Sarah。

HTML 的 value 这个 attribute 指定了初始值;DOM 的 value 这个 property 是当前值。

示例 2:禁用按钮

disabled Attribute 是另一个例子。按钮的 disabled Property 默认为 false,因此按钮是启用的。

当你添加 disabled Attribute 时,仅仅它的出现就将按钮的 disabled Property 初始化成了 true,因此该按钮就被禁用了。

<button disabled>Test Button</button>

添加和删除 disabled , Attribute 会禁用和启用该按钮。 但是,Attribute 的值无关紧要,这就是为什么你不能通过编写 <button disabled="false">仍被禁用</button> 来启用此按钮的原因。

要控制按钮的状态,请设置 disabled Property。

虽然技术上说你可以设置 [attr.disabled] 属性绑定,但是它们的值是不同的,Property 绑定要求一个布尔值,而其相应的 Attribute 绑定则取决于该值是否为 null。例子如下:

&
lt;input [disabled]="condition ? true : false"&
lt;input [attr.disabled]="condition ? 'disabled' : null"&

通常,要使用 Property 绑定而不是 Attribute 绑定,因为它更直观(是一个布尔值),语法更短,并且性能更高。

绑定类型与绑定目标

数据绑定的目标是 DOM 中的对象。 根据绑定类型,该目标可以是 Property 名(元素、组件或指令的)、事件名(元素、组件或指令的),有时是 Attribute 名。下表中总结了不同绑定类型的目标。

  1. 绑定类型:属性。

  • 目标:元素的 property 、组件的 property 、指令的 property 。

  • 示例:

    <img [src]="heroImageUrl">
    <app-hero-detail [hero]="currentHero"></app-hero-detail>
    <div [ngClass]="{'special': isSpecial}"></div>

  1. 绑定类型:事件。

  • 目标:元素的事件、组件的事件、指令的事件。

  • 示例:

    <button (click)="onSave()">Save</button>
    <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
    <div (myClick)="clicked=$event" clickable>click me</div>

  1. 绑定类型:双向。

  • 目标:事件与 property。

  • 示例:

    <input [(ngModel)]="name">

  1. 绑定类型:Attribute 。

  • 目标:attribute(例外情况)。

  • 示例:

    <button [attr.aria-label]="help">help</button>

  1. 绑定类型:CSS 类。

  • 目标:class property 。

  • 示例:

    <div [class.special]="isSpecial">Special</div>

  1. 绑定类型:样式。

  • 目标:style property 。

  • 示例:

    <button [style.color]="isSpecial ? 'red' : 'green'">

Property 绑定 [property]

使用 Property 绑定到目标元素或指令 @Input() 装饰器的 set 型属性。

单向输入

Property 绑定的值在一个方向上流动,从组件的 Property 变为目标元素的 Property。

你不能使用属性绑定从目标元素读取或拉取值。同样的,你也不能使用属性绑定在目标元素上调用方法。如果元素要引发事件,则可以使用事件绑定来监听它们。

示例:

最常见的 Property 绑定将元素的 Property 设置为组件的 Property 值。例子之一是将 img 元素的 src Property 绑定到组件的 itemImageUrl Property:

Path:"src/app/app.component.html"

<img [src]="itemImageUrl">

这是绑定到 colSpan Property 的示例。请注意,它不是 colspan,后者是 Attribute,用小写的 s 拼写。

Path:"src/app/app.component.html"

<!-- Notice the colSpan property is camel case -->
<tr><td [colSpan]="2">Span 2 columns</td></tr>

另一个例子是当组件说它 isUnchanged(未改变)时禁用按钮:

Path:"src/app/app.component.html"

<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Disabled Button</button>

另一个例子是设置指令的属性:

Path:"src/app/app.component.html"

<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p

另一种方法是设置自定义组件的模型属性 —— 这是一种父级和子级组件进行通信的好办法:

Path:"src/app/app.component.html"

<app-item-detail [childItem]="parentItem"></app-item-detail>

绑定目标

包裹在方括号中的元素属性名标记着目标属性。下列代码中的目标属性是 image 元素的 src 属性。

Path:"src/app/app.component.html"

<img [src]="itemImageUrl">

还有一种使用 bind- 前缀的替代方案:

Path:"src/app/app.component.html"

<img bind-src="itemImageUrl">

在大多数情况下,目标名都是 Property 名,虽然它看起来像 Attribute 名。因此,在这个例子中,src 是 <img> 元素属性的名称。

元素属性可能是最常见的绑定目标,但 Angular 会先去看这个名字是否是某个已知指令的属性名,就像下面的例子中一样:

Path:"src/app/app.component.html"

<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p>

从技术上讲,Angular 将这个名称与指令的 @Input() 进行匹配,它来自指令的 inputs 数组中列出的 Property 名称之一或是用 @Input() 装饰的属性。这些输入都映射到指令自身的属性。

如果名字没有匹配上已知指令或元素的属性,Angular 就会报告“未知指令”的错误。

注:
- 尽管目标名称通常是 Property 的名称,但是在 Angular 中,有几个常见属性会自动将 Attribute 映射为 Property。这些包括 class / className,nnerHtml / innerHTML 和 tabindex / abIndex。

消除副作用

模板表达的计算应该没有明显的副作用。表达式语言本身或你编写模板表达式的方式在一定程度上有所帮助。你不能为属性绑定表达式中的任何内容赋值,也不能使用递增和递减运算符。

例如,假设你有一个表达式,该表达式调用了具有副作用的属性或方法。该表达式可以调用类似 getFoo() 的函数,只有你知道 getFoo() 做了什么。如果 getFoo() 更改了某些内容,而你恰巧绑定到该内容,则 Angular 可能会也可能不会显示更改后的值。Angular 可能会检测到更改并抛出警告错误。最佳实践是坚持使用属性和返回值并避免副作用的方法。

返回正确的类型

模板表达式的计算结果应该是目标属性所需要的值类型。如果 target 属性需要一个字符串,则返回一个字符串;如果需要一个数字,则返回一个数字;如果需要一个对象,则返回一个对象,依此类推。

在下面的例子中,temDetailComponent 的 childItem 属性需要一个字符串,而这正是你要发送给属性绑定的内容:

Path:"src/app/app.component.html"

<app-item-detail [childItem]="parentItem"></app-item-detail>

你可以查看 ItemDetailComponent 来确认这一点,它的 @Input 类型设为了字符串:

Path:"src/app/item-detail/item-detail.component.ts (setting the @Input() type)"

@Input() childItem: string;

如你所见,AppComponent 中的 parentItem 是一个字符串,而 ItemDetailComponent 需要的就是字符串:

Path:"src/app/app.component.ts"

parentItem = 'lamp';

传入对象

前面的简单示例演示了传入字符串的情况。要传递对象,其语法和思想是相同的。

在这种情况下,ItemListComponent 嵌套在 AppComponent 中,并且 items 属性需要一个对象数组。

Path:"src/app/app.component.html"

<app-item-list [items]="currentItems"></app-item-list>

items 属性是在 ItemListComponent 中用 Item 类型声明的,并带有 @Input() 装饰器:

Path:"src/app/item-list.component.ts"

@Input() items: Item[];

在此示例应用程序中,Item是具有两个属性的对象。一个 id 和一个 name。

Path:"src/app/item.ts"

export interface Item {
  id: number;
  name: string;
}

当另一个文件 "mock-items.ts" 中存在一个条目列表时,你可以在 "app.component.ts" 中指定另一个条目,以便渲染新条目:

Path:"src/app.component.ts"

currentItems = [{
  id: 21,
  name: 'phone'
}];

在这个例子中,你只需要确保你所提供的对象数组的类型,也就是这个 Item 的类型是嵌套组件 `ItemListComponent 所需要的类型。

在此示例中,AppComponen 指定了另一个 item 对象( urrentItems )并将其传给嵌套的 ItemListComponent。ItemListComponent 之所以能够使用 currentItems 是因为它与 "item.ts" 中定义的 Item 对象的类型相匹配。在 "item.ts" 文件中,ItemListComponent 获得了其对 item 的定义。

方括号

方括号 [] 告诉 Angular 计算该模板表达式。如果省略括号,Angular 会将字符串视为常量,并使用该字符串初始化目标属性 :

Path:"src/app.component.html"

<app-item-detail childItem="parentItem"></app-item-detail>

省略方括号将渲染字符串 parentItem,而不是 parentItem 的值。

一次性字符串初始化

当满足下列条件时,应该省略括号:

  • 目标属性接受字符串值。

  • 字符串是一个固定值,你可以直接将其放入模板中。

  • 这个初始值永不改变。

你通常会以这种方式在标准 HTML 中初始化属性,并且它对指令和组件的属性初始化同样有效。 下面的示例将 StringInitComponent 中的 prefix 属性初始化为固定字符串,而不是模板表达式。Angular 设置它,然后就不管它了。

Path:"src/app/app.component.html"

<app-string-init prefix="This is a one-time initialized string."></app-string-init>

另一方面,[item] 绑定仍然是与组件的 currentItems 属性的实时绑定。

属性绑定与插值

你通常得在插值和属性绑定之间做出选择。 下列这几对绑定做的事情完全相同:

Path:"src/app/app.component.html"

<p><img src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="itemImageUrl"> is the <i>property bound</i> image.</p>


<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>

在许多情况下,插值是属性绑定的便捷替代法。当要把数据值渲染为字符串时,虽然可读性方面倾向于插值,但没有技术上的理由偏爱一种形式。但是,将元素属性设置为非字符串的数据值时,必须使用属性绑定。

内容安全

假设如下恶意内容:

Path:"src/app/app.component.ts"

evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';

在组件模板中,内容可以与插值一起使用:

Path:"src/app/app.component.html"

<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>

幸运的是,Angular 数据绑定对于危险的 HTML 高度戒备。在上述情况下,HTML 将按原样显示,而 Javascript 不执行。Angular 不允许带有 script 标签的 HTML 泄漏到浏览器中,无论是插值还是属性绑定。

不过,在下列示例中,Angular 会在显示值之前先对它们进行无害化处理。

Path:"src/app/app.component.html"

<!--
 Angular generates a warning for the following line as it sanitizes them
 WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
-->
 <p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>

插值处理 <script> 标记与属性绑定的方式不同,但是这两种方法都可以使内容无害。以下是 evilTitle 示例的浏览器输出。

"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
"Template Syntax" is the property bound evil title.

attribute、class 和 style 绑定

模板语法为那些不太适合使用属性绑定的场景提供了专门的单向数据绑定形式。

要在运行中的应用查看 Attribute 绑定、类绑定和样式绑定,请参见 现场演练 / 下载范例 特别是对于本节。

attribute 绑定

可以直接使用 Attribute 绑定设置 Attribute 的值。一般来说,绑定时设置的是目标的 Property,而 Attribute 绑定是唯一的例外,它创建和设置的是 Attribute。

通常,使用 Property 绑定设置元素的 Property 优于使用字符串设置 Attribute。但是,有时没有要绑定的元素的 Property,所以其解决方案就是 Attribute 绑定。

考虑 ARIA 和 SVG。它们都纯粹是 Attribute,不对应于元素的 Property,也不能设置元素的 Property。 在这些情况下,就没有要绑定到的目标 Property。

Attribute 绑定的语法类似于 Property 绑定,但其括号之间不是元素的 Property,而是由前缀 attr、点( . )和 Attribute 名称组成。然后,你就可以使用能解析为字符串的表达式来设置该 Attribute 的值,或者当表达式解析为 null 时删除该 Attribute。

attribute 绑定的主要用例之一是设置 ARIA attribute(译注:ARIA 指无障碍功能,用于给残障人士访问互联网提供便利), 就像这个例子中一样:

Path:"src/app/app.component.html"

<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>

colspan 和 colSpan
注意 colspan Attribute 和 colSpan Property 之间的区别。

如果你这样写:

&
lt;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 这个 Property。这是正确的,因为 colspan 是一个 Attribute,而 colSpan (colSpan 中的 S 是大写)则是相应的 Property。插值和 Property 绑定只能设置 Property,不能设置 Attribute。

相反,你可以使用 Property 绑定并将其改写为:

&Path:"src/app/app.component.html"

lt;!-- Notice the colSpan property is camel case --&
lt;tr&<td [colSpan]="1 + 1"&Three-Four</td&</tr&

类绑定

下面是在普通 HTML 中不用绑定来设置 class Attribute 的方法:

<!-- standard class attribute setting -->


<div class="foo bar">Some text</div>

你还可以使用类绑定来为一个元素添加和移除 CSS 类。

要创建单个类的绑定,请使用 class 前缀,紧跟一个点(.),再跟上 CSS 类名,比如 [class.foo]="hasFoo"。 当绑定表达式为真值的时候,Angular 就会加上这个类,为假值则会移除,但 undefined 是假值中的例外,参见样式委派 部分。

要想创建多个类的绑定,请使用通用的 [class] 形式来绑定类,而不要带点,比如 [class]="classExpr"。 该表达式可以是空格分隔的类名字符串,或者用一个以类名为键、真假值表达式为值的对象。 当使用对象格式时,Angular 只会加上那些相关的值为真的类名。

一定要注意,在对象型表达式中(如 object、Array、Map、Set 等),当这个类列表改变时,对象的引用也必须修改。仅仅修改其属性而不修改对象引用是无法生效的。

如果有多处绑定到了同一个类名,出现的冲突将根据样式的优先级规则进行解决。

绑定类型 语法 输入类型 输入值示例
单个类绑定 [class.foo]="hasFoo" boolean OR undefined OR null true, false
多个类绑定 [class]="classExpr" string OR {[key: string]: boolean / undefined / null} OR Array<string> | "my-class-1 my-class-2 my-class-3" OR {foo: true, bar: false} OR ['foo', 'bar']`

注:
- 多个类绑定的输入类型按顺序对应一个示例,而不是一个类型对应多个示例。

尽管此技术适用于切换单个类名,但在需要同时管理多个类名时请考虑使用 NgClass 指令。

样式绑定

下面演示了如何不通过绑定在普通 HTML 中设置 style 属性:

<!-- standard style attribute setting -->
<div style="color: blue">Some text</div>

你还可以通过样式绑定来动态设置样式。

要想创建单个样式的绑定,请以 style 前缀开头,紧跟一个点(.),再跟着 CSS 样式的属性名,比如 [style.width]="width"。 该属性将会被设置为绑定表达式的值,该值通常为字符串。 不过你还可以添加一个单位表达式,比如 em 或 %,这时候该值就要是一个 number 类型。

注:
- 样式属性命名方法可以用中线命名法,像上面的一样 也可以用驼峰式命名法,如 fontSize。

如果要切换多个样式,你可以直接绑定到 [style] 属性而不用点(比如,[style]="styleExpr")。赋给 [style] 的绑定表达式通常是一系列样式组成的字符串,比如 "width: 100px; height: 100px;"。

你也可以把该表达式格式化成一个以样式名为键、以样式值为值的对象,比如 {width: '100px', height: '100px'}。一定要注意,对于任何对象型的表达式( 如 object,Array,Map,Set 等),当这个样式列表改变时,对象的引用也必须修改。仅仅修改其属性而不修改对象引用是无法生效的。。

如果有多处绑定了同一个样式属性,则会使用样式的优先级规则来解决冲突。

绑定类型 语法 输入类型 输入值示例
单一样式绑定 [style.width]="width" string OR undefined OR null "100px"
带单位的单一样式绑定 [style.width.px]="width" number OR undefined OR null 100
多个样式绑定 [style]="styleExpr" string OR {[key: string]: string / undefined / null} OR Array<string>` ['width', '100px']

NgStyle 指令可以作为 [style] 绑定的替代指令。但是,应该把上面这种 [style] 样式绑定语法作为首选,因为随着 Angular 中样式绑定的改进,NgStyle 将不再提供重要的价值,并最终在未来的某个版本中删除。

样式的优先级规则

一个 HTML 元素可以把它的 CSS 类列表和样式值绑定到多个来源(例如,来自多个指令的宿主 host 绑定)。

当对同一个类名或样式属性存在多个绑定时,Angular 会使用一组优先级规则来解决冲突,并确定最终哪些类或样式会应用到该元素中。

样式的优先级规则(从高到低)

  1. 模板绑定

  1. 属性绑定(例如 <div [class.foo]="hasFoo"& 或 <div [style.color]="color"&)

1. Map 绑定(例如,<div [class]="classExpr"& 或 <div [style]="styleExpr"& )

2. 静态值(例如 <div class="foo"& 或 <div style="color: blue"& )

  1. 指令宿主绑定

  1. 属性绑定(例如,host: {'[class.foo]': 'hasFoo'} 或 host: {'[style.color]': 'color'} )

1. Map 绑定(例如,host: {'[class]': 'classExpr'} 或者 host: {'[style]': 'styleExpr'} )

2. 静态值(例如,host: {'class': 'foo'} 或 host: {'style': 'color: blue'} )

  1. 组件宿主绑定

  1. 属性绑定(例如,host: {'[class.foo]': 'hasFoo'} 或 host: {'[style.color]': 'color'} )

1. Map 绑定(例如,host: {'[class]': 'classExpr'} 或者 host: {'[style]': 'styleExpr'} )

2. 静态值(例如,host: {'class': 'foo'} 或 host: {'style': 'color: blue'} )

某个类或样式绑定越具体,它的优先级就越高。

对具体类(例如 [class.foo] )的绑定优先于一般化的 [class] 绑定,对具体样式(例如 [style.bar] )的绑定优先于一般化的 [style] 绑定。

Path:"src/app/app.component.html"

<h3>Basic specificity</h3>


<!-- The `class.special` binding will override any value for the `special` class in `classExpr`.  -->
<div [class.special]="isSpecial" [class]="classExpr">Some text.</div>


<!-- The `style.color` binding will override any value for the `color` property in `styleExpr`.  -->
<div [style.color]="color" [style]="styleExpr">Some text.</div>

当处理不同来源的绑定时,也适用这种基于具体度的规则。 某个元素可能在声明它的模板中有一些绑定、在所匹配的指令中有一些宿主绑定、在所匹配的组件中有一些宿主绑定。

模板中的绑定是最具体的,因为它们直接并且唯一地应用于该元素,所以它们具有最高的优先级。

指令的宿主绑定被认为不太具体,因为指令可以在多个位置使用,所以它们的优先级低于模板绑定。

指令经常会增强组件的行为,所以组件的宿主绑定优先级最低。

Path:"src/app/app.component.html"

<h3>Source specificity</h3>


<!-- The `class.special` template binding will override any host binding to the `special` class set by `dirWithClassBinding` or `comp-with-host-binding`.-->
<comp-with-host-binding [class.special]="isSpecial" dirWithClassBinding>Some text.</comp-with-host-binding>


<!-- The `style.color` template binding will override any host binding to the `color` property set by `dirWithStyleBinding` or `comp-with-host-binding`. -->
<comp-with-host-binding [style.color]="color" dirWithStyleBinding>Some text.</comp-with-host-binding>

另外,绑定总是优先于静态属性。

在下面的例子中,class 和 [class] 具有相似的具体度,但 [class] 绑定优先,因为它是动态的。

Path:"src/app/app.component.html"

<h3>Dynamic vs static</h3>


<!-- If `classExpr` has a value for the `special` class, this value will override the `class="special"` below -->
<div class="special" [class]="classExpr">Some text.</div>


<!-- If `styleExpr` has a value for the `color` property, this value will override the `style="color: blue"` below -->
<div style="color: blue" [style]="styleExpr">Some text.</div>

委托优先级较低的样式

更高优先级的样式可以使用 undefined 值“委托”给低级的优先级样式。虽然把 style 属性设置为 null 可以确保该样式被移除,但把它设置为 undefined 会导致 Angular 回退到该样式的次高优先级。

例如,考虑以下模板:

Path:"src/app/app.component.html"

<comp-with-host-binding dirWithHostBinding></comp-with-host-binding>

想象一下,dirWithHostBinding 指令和 comp-with-host-binding 组件都有 [style.width] 宿主绑定。在这种情况下,如果 dirWithHostBinding 把它的绑定设置为 undefined,则 width 属性将回退到 comp-with-host-binding 主机绑定的值。但是,如果 dirWithHostBinding 把它的绑定设置为 null,那么 width 属性就会被完全删除。

事件绑定 (event)

事件绑定允许你监听某些事件,比如按键、鼠标移动、点击和触屏。

Angular 的事件绑定语法由等号左侧带圆括号的目标事件和右侧引号中的模板语句组成。 下面事件绑定监听按钮的点击事件。每当点击发生时,都会调用组件的 onSave() 方法。

目标事件

如前所述,其目标就是此按钮的单击事件。

Path:"src/app/app.component.html"

<button (click)="onSave($event)">Save</button>

有些人更喜欢带 on- 前缀的备选形式,称之为规范形式:

Path:"src/app/app.component.html"

<button on-click="onSave($event)">on-click Save</button>

元素事件可能是更常见的目标,但 Angular 会先看这个名字是否能匹配上已知指令的事件属性,就像下面这个例子:

Path:"src/app/app.component.html"

<h4>myClick is an event on the custom ClickDirective:</h4>
<button (myClick)="clickMessage=$event" clickable>click with myClick</button>
{{clickMessage}}

如果这个名字没能匹配到元素事件或已知指令的输出属性,Angular 就会报“未知指令”错误。

$event 和事件处理语句

在事件绑定中,Angular 会为目标事件设置事件处理器。

当事件发生时,这个处理器会执行模板语句。 典型的模板语句通常涉及到响应事件执行动作的接收器,例如从 HTML 控件中取得值,并存入模型。

绑定会通过名叫 $event 的事件对象传递关于此事件的信息(包括数据值)。

事件对象的形态取决于目标事件。如果目标事件是原生 DOM 元素事件, $event 就是 DOM 事件对象,它有像 target 和 target.value 这样的属性。

考虑这个示例:

Path:"src/app/app.component.html"

<input [value]="currentItem.name"
       (input)="currentItem.name=$event.target.value" >
without NgModel

上面的代码在把输入框的 value 属性绑定到 name 属性。 要监听对值的修改,代码绑定到输入框的 input 事件。 当用户造成更改时,input 事件被触发,并在包含了 DOM 事件对象 ($event) 的上下文中执行这条语句。

要更新 name 属性,就要通过路径 $event.target.value 来获取更改后的值。

如果事件属于指令(回想一下,组件是指令的一种),那么 $event 具体是什么由指令决定。

使用 EventEmitter 实现自定义事件

通常,指令使用 Angular EventEmitter 来触发自定义事件。 指令创建一个 EventEmitter 实例,并且把它作为属性暴露出来。 指令调用 EventEmitter.emit(payload) 来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过 $event 对象来访问载荷。

假设 ItemDetailComponent 用于显示英雄的信息,并响应用户的动作。 虽然 ItemDetailComponent 包含删除按钮,但它自己并不知道该如何删除这个英雄。 最好的做法是触发事件来报告“删除用户”的请求。

下面的代码节选自 ItemDetailComponent:

Path:"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>

Path:"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 发出一个 Item 对象。

现在,假设有个宿主的父组件,它绑定了 ItemDetailComponent 的 deleteRequest 事件。

Path:"src/app/app.component.html (event-binding-to-component)"

<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>

当 deleteRequest 事件触发时,Angular 调用父组件的 deleteItem 方法, 在 $event 变量中传入要删除的英雄(来自 ItemDetail)。

模板语句有副作用

虽然模板表达式不应该有副作用,但是模板语句通常会有。这里的 deleteItem() 方法就有一个副作用:它删除了一个条目。

删除这个英雄会更新模型,还可能触发其它修改,包括向远端服务器的查询和保存。 这些变更通过系统进行扩散,并最终显示到当前以及其它视图中。

双向绑定 [(...)]

双向绑定为你的应用程序提供了一种在组件类及其模板之间共享数据的方式。

双向绑定的基础知识

双向绑定会做两件事:

  1. 设置特定的元素属性。

  1. 监听元素的变更事件。

Angular 为此提供了一种特殊的双向数据绑定语法 [()]。[()] 语法将属性绑定的括号 [] 与事件绑定的括号 () 组合在一起。

[()] 语法很容易想明白:该元素具有名为 x 的可设置属性和名为 xChange 的相应事件。 SizerComponent 就是用的这种模式。它具有一个名为 size 的值属性和一个与之相伴的 sizeChange 事件:

Path:"src/app/sizer.component.ts"

import { Component, Input, Output, EventEmitter } from '@angular/core';


@Component({
  selector: 'app-sizer',
  templateUrl: './sizer.component.html',
  styleUrls: ['./sizer.component.css']
})
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);
  }


}

Path:"src/app/sizer.component.html"

<div>
  <button (click)="dec()" title="smaller">-</button>
  <button (click)="inc()" title="bigger">+</button>
  <label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>

size 的初始值来自属性绑定的输入值。单击按钮可在最小值/最大值范围内增大或减小 size,然后带上调整后的大小发出 sizeChange 事件。

下面的例子中,AppComponent.fontSize 被双向绑定到 SizerComponent:

Path:"src/app/app.component.html (two-way-1)"

<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

AppComponent.fontSizePx 建立初始 SizerComponent.size 值。

Path:"src/app/app.component.ts"

fontSizePx = 16;

单击按钮就会通过双向绑定更新 AppComponent.fontSizePx。修改后的 AppComponent.fontSizePx 值将传递到样式绑定,从而使显示的文本更大或更小。

双向绑定语法实际上是属性绑定和事件绑定的语法糖。 Angular 将 izerComponent 的绑定分解成这样:

Path:"src/app/app.component.html (two-way-2)"

<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>

$event 变量包含了 SizerComponent.sizeChange 事件的荷载。 当用户点击按钮时,Angular 将 $event 赋值给 AppComponent.fontSizePx。

表单中的双向绑定

与单独的属性绑定和事件绑定相比,双向绑定语法非常方便。将双向绑定与 HTML 表单元素(例如 <input> 和 <select>)一起使用会很方便。但是,没有哪个原生 HTML 元素会遵循 x 值和 xChange 事件的命名模式。

内置指令

Angular 提供了两种内置指令:属性型指令和结构型指令。

内置属性型指令

属性型指令会监听并修改其它 HTML 元素和组件的行为、Attribute 和 Property。 它们通常被应用在元素上,就好像它们是 HTML 属性一样,因此得名属性型指令。

许多 NgModule(例如 RouterModule 和 FormsModule )都定义了自己的属性型指令。最常见的属性型指令如下:

  1. NgClass —— 添加和删除一组 CSS 类。

用 ngClass 同时添加或删除几个 CSS 类。

Path:"src/app/app.component.html"

    <!-- toggle the "special" class on/off with a property -->
    <div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>

注:
- 要添加或删除单个类,请使用类绑定而不是 NgClass 。

考虑一个 setCurrentClasses() 组件方法,该方法设置一个组件属性 currentClasses,该对象具有一个根据其它三个组件属性的 true / false 状态来添加或删除三个 CSS 类的对象。该对象的每个键(key)都是一个 CSS 类名。如果要添加上该类,则其值为 true,反之则为 false。

Path:"src/app/app.component.html"

    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 类:

Path:"src/app/app.component.html"

    <div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>

注:
- 请记住,在这种情况下,你要在初始化时和它依赖的属性发生变化时调用 setCurrentClasses()。

  1. NgStyle —— 添加和删除一组 HTML 样式。

使用 NgStyle 根据组件的状态同时动态设置多个内联样式。

不用 NgStyle

有些情况下,要考虑使用样式绑定来设置单个样式值,而不使用 NgStyle。

Path:"src/app/app.component.html"

    <div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
      This div is x-large or smaller.
    </div>

但是,如果要同时设置多个内联样式,请使用 NgStyle 指令。

下面的例子是一个 setCurrentStyles() 方法,它基于该组件另外三个属性的状态,用一个定义了三个样式的对象设置了 currentStyles 属性。

Path:"src/app/app.component.ts"

    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,来根据它设置此元素的样式:

Path:"src/app/app.component.html"

    <div [ngStyle]="currentStyles">
      This div is initially italic, normal weight, and extra large (24px).
    </div>

注:
- 请记住,无论是在初始时还是其依赖的属性发生变化时,都要调用 setCurrentStyles()。

  1. NgModel —— 将数据双向绑定添加到 HTML 表单元素。

NgModel 指令允许你显示数据属性并在用户进行更改时更新该属性。这是一个例子:

Path:"src/app/app.component.html (NgModel example)"

    <label for="example-ngModel">[(ngModel)]:</label>
    <input [(ngModel)]="currentItem.name" id="example-ngModel">

导入 FormsModule 以使用 ngModel

要想在双向数据绑定中使用 ngModel 指令,必须先导入 FormsModule 并将其添加到 NgModule 的 imports 列表中。要了解关于 FormsModule 和 ngModel 的更多信息,参见表单一章。

记住,要导入 FormsModule 才能让 [(ngModel)] 可用,如下所示:

Path:"src/app/app.module.ts (FormsModule import)"

    import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
    /* . . . */
    @NgModule({
    /* . . . */


      imports: [
        BrowserModule,
        FormsModule // <--- import into the NgModule
      ],
    /* . . . */
    })
    export class AppModule { }

通过分别绑定到 <input> 元素的 value 属性和 input 事件,可以达到同样的效果:

Path:"src/app/app.component.html"

    <label for="without">without NgModel:</label>
    <input [value]="currentItem.name" (input)="currentItem.name=$event.target.value" id="without">

为了简化语法,ngModel 指令把技术细节隐藏在其输入属性 ngModel 和输出属性 ngModelChange 的后面:

Path:"src/app/app.component.html"

    <label for="example-change">(ngModelChange)="...name=$event":</label>
    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">

ngModel 输入属性会设置该元素的值,并通过 ngModelChange 的输出属性来监听元素值的变化。

NgModel 和值访问器

这些技术细节是针对每种具体元素的,因此 NgModel 指令仅适用于通过 ControlValueAccessor 适配过这种协议的元素。Angular 已经为所有基本的 HTML 表单元素提供了值访问器,表单一章示范了如何绑定到它们。

在编写适当的值访问器之前,不能将 [(ngModel)] 应用于非表单的原生元素或第三方自定义组件。欲知详情,参见DefaultValueAccessor上的 API 文档。

你不一定非用为所编写的 Angular 组件提供值访问器,因为你还可以把值属性和事件属性命名为符合 Angular 的基本双向绑定语法的形式,并完全跳过 NgModel。双向绑定部分的 sizer 是此技术的一个示例。

单独的 ngModel 绑定是对绑定到元素的原生属性方式的一种改进,但你可以使用 [(ngModel)] 语法来通过单个声明简化绑定:

Path:"src/app/app.component.html"

    <label for="example-ngModel">[(ngModel)]:</label>
    <input [(ngModel)]="currentItem.name" id="example-ngModel">

此 [(ngModel)] 语法只能设置数据绑定属性。如果你要做得更多,可以编写扩展表单。例如,下面的代码将 <input> 值更改为大写:

Path:"src/app/app.component.html"

    <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">

这里是所有这些变体的动画,包括这个大写转换的版本:

内置结构型指令

结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,这通常是通过添加、移除和操纵它们所附加到的宿主元素来实现的。

常见的内置结构型指令:

  1. NgIf —— 从模板中创建或销毁子视图。

你可以通过将 NgIf 指令应用在宿主元素上来从 DOM 中添加或删除元素。在此示例中,将指令绑定到了条件表达式,例如 isActive。

Path:"src/app/app.component.html"

    <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>

注:
- 不要忘了 ngIf 前面的星号(*)。

当 isActive 表达式返回真值时,NgIf 会把 ItemDetailComponent 添加到 DOM 中。当表达式为假值时,NgIf 将从 DOM 中删除 ItemDetailComponent,从而销毁该组件及其所有子组件。

显示/隐藏与 NgIf

隐藏元素与使用 NgIf 删除元素不同。为了进行比较,下面的示例演示如何使用类或样式绑定来控制元素的可见性。

Path:"src/app/app.component.html"

    <!-- isSpecial is true -->
    <div [class.hidden]="!isSpecial">Show with class</div>
    <div [class.hidden]="isSpecial">Hide with class</div>


    <p>ItemDetail is in the DOM but hidden</p>
    <app-item-detail [class.hidden]="isSpecial"></app-item-detail>


    <div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
    <div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div>

隐藏元素时,该元素及其所有后代仍保留在 DOM 中。这些元素的所有组件都保留在内存中,Angular 会继续做变更检查。它可能会占用大量计算资源,并且会不必要地降低性能。

NgIf 工作方式有所不同。如果 NgIf 为 false,则 Angular 将从 DOM 中删除该元素及其后代。这销毁了它们的组件,释放了资源,从而带来更好的用户体验。

如果要隐藏大型组件树,请考虑使用 NgIf 作为显示/隐藏的更有效替代方法。

防范空指针错误

ngIf 另一个优点是你可以使用它来防范空指针错误。显示/隐藏就是最合适的极简用例,当你需要防范时,请改用 ngIf 代替。如果其中嵌套的表达式尝试访问 null 的属性,Angular 将引发错误。

下面的例子中 NgIf 保护着两个 <div>。仅当存在 currentCustomer 时,才会显示 currentCustomer 名称。除非它为 null 否则不会显示 nullCustomer。

Path:"src/app/app.component.html"

    <div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>

Path:"src/app/app.component.html"

    <div *ngIf="nullCustomer">Hello, <span>{{nullCustomer}}</span></div>

  1. NgFor —— 为列表中的每个条目重复渲染一个节点。

NgFor 是一个重复器指令 —— 一种用来显示条目列表的方法。你定义了一个 HTML 块,该 HTML 块定义了应如何显示单个条目,然后告诉 Angular 以该块为模板来渲染列表中的每个条目。赋值给 *ngFor 的文本是用来指导重复器工作过程的指令。

以下示例显示了如何将 NgFor 应用于简单的 <div>。(不要忘了 ngFor 前面的星号(*)。)

Path:"src/app/app.component.html"

    <div *ngFor="let item of items">{{item.name}}</div>

你还可以将 NgFor 应用于组件元素,如以下示例所示。

Path:"src/app/app.component.html"

    <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>

*NGFOR 微语法
&赋值给 *ngFor 的字符串不是模板表达式。而是一个微语法 —— 由 Angular 解释的一种小型语言。字符串 "let item of items" 的意思是:

&&将 items 数组中的每个条目存储在局部循环变量 item 中,并使其可用于每次迭代的模板 HTML 中。

&Angular 将该指令转换为包裹着宿主元素的 <ng-template&,然后反复使用此模板为列表中的每个 item 创建一组新的元素和绑定。

模板输入变量

item 前面的 let 关键字创建了一个名为 item 的模板输入变量。ngFor 指令迭代父组件的 items 属性所返回的 items 数组,并在每次迭代期间将 item 设置为该数组中的当前条目。

ngFor 的宿主元素及其后代中可引用 item,来访问该条目的属性。以下示例首先在插值中引用 item,然后把一个绑定表达式传入 <app-item-detail> 组件的 item 属性。

Path:"src/app/app.component.html"

    <div *ngFor="let item of items">{{item.name}}</div>
    <!-- . . . -->
      <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>

*ngFor 与 index

NgFor 指令上下文中的 index 属性在每次迭代中返回该条目的从零开始的索引。 你可以在模板输入变量中捕获 index,并在模板中使用它。

下面的例子在名为 i 的变量中捕获 index,并将其与条目名称一起显示。

Path:"src/app/app.component.ts"

    <div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>

*带 trackBy 的 ngFor**

如果将 NgFor 与大型列表一起使用,则对某个条目的较小更改(例如删除或添加一项)就会触发一系列 DOM 操作。 例如,重新查询服务器可能会重置包含所有新条目对象的列表,即使先前已显示这些条目也是如此。在这种情况下,Angular 只能看到由新的对象引用组成的新列表,它别无选择,只能用所有新的 DOM 元素替换旧的 DOM 元素。

你可以使用 trackBy 来让它更加高效。向该组件添加一个方法,该方法返回 NgFor 应该跟踪的值。这个例子中,该值是英雄的 id。如果 id 已经被渲染,Angular 就会跟踪它,而不会重新向服务器查询相同的 id。

Path:"src/app/app.component.html"

    <div *ngFor="let item of items; trackBy: trackByItems">
      ({{item.id}}) {{item.name}}
    </div>

这就是 trackBy 效果的说明。“Reset items” 将创建具有相同 item.id 的新条目。“Change ids” 将使用新的 item.id 创建新条目。

如果没有 trackBy,这些按钮都会触发完全的 DOM 元素替换。

有了 trackBy,则只有修改了 id 的按钮才会触发元素替换。

注:
- 内置指令仅仅使用了公共 API。也就是说,它们没有用到任何其它指令无权访问的私有 API。

  1. NgSwitch —— 一组在备用视图之间切换的指令。

NgSwitch 类似于 JavaScript switch 语句。它根据切换条件显示几个可能的元素中的一个。Angular 只会将选定的元素放入 DOM。

NgSwitch 实际上是三个协作指令的集合: NgSwitch,NgSwitchCase 和 NgSwitchDefault,如以下示例所示。

Path:"src/app/app.component.html"

    <div [ngSwitch]="currentItem.feature">
      <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>
      <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>
      <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>
      <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>
    <!-- . . . -->
      <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item>
    </div>

NgSwitch 是控制器指令。把它绑定到一个返回开关值的表达式,例如 feature。尽管此示例中的 feature 值是字符串,但开关值可以是任何类型。

绑定到 [ngSwitch]。如果试图写成 *ngSwitch,就会出现错误,因为 NgSwitch 是属性型指令,而不是结构型指令。它不会直接接触 DOM,而是会更改与之相伴的指令的行为。

绑定到 *ngSwitchCase 和 *ngSwitchDefault 、NgSwitchCase 和 NgSwitchDefault 指令都是结构型指令,因为它们会从 DOM 中添加或移除元素。

  • 当 NgSwitchCase 的绑定值等于开关值时,就将其元素添加到 DOM 中;否则从 DOM 中删除。

  • NgSwitchDefault 会在没有任何一个 NgSwitchCase 被选中时把它所在的元素加入 DOM 中。

开关指令对于添加和删除组件元素特别有用。本示例在 "item-switch.components.ts" 文件中定义的四个 item 组件之间切换。每个组件都有一个名叫 item 的输入属性,它会绑定到父组件的 currentItem。

开关指令也同样适用于原生元素和 Web Component。 比如,你可以把 <app-best-item> 分支替换为如下代码。

Path:"src/app/app.component.html"

    <div *ngSwitchCase="'bright'"> Are you as bright as {{currentItem.name}}?</div>

结构型指令一小节涵盖了结构型指令的详细内容,它解释了以下内容:

  • 为什么在要指令名称前加上星号(*)。

  • 当指令没有合适的宿主元素时,使用 <ng-container& 对元素进行分组。

  • 如何写自己的结构型指令。

  • 你只能往一个元素上应用一个结构型指令。

模板引用变量( #var )

模板引用变量通常是对模板中 DOM 元素的引用。它还可以引用指令(包含组件)、元素、TemplateRef 或 Web Component。

使用井号(#)声明模板引用变量。以下模板引用变量 #phone 会在 <input> 元素上声明了一个 phone 变量。

Path:"src/app/app.component.html"

<input #phone placeholder="phone number" />

你可以在组件模板中的任何位置引用模板引用变量。这个例子中,模板下方的 <button> 就引用了 phone 变量。

Path:"src/app/app.component.html"

<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>

模板引用变量如何取得它本身的值

在大多数情况下,Angular 会将模板引用变量的值设置为声明该变量的元素。在上一个示例中,phone 指的是电话号码的 <input>。按钮的单击处理程序将把这个 <input> 的值传给组件的 callPhone() 方法。

NgForm 指令可以更改该行为并将该值设置为其它值。在以下示例中,模板引用变量 itemForm 出现了 3 次,由 HTML 分隔。

Path:"src/app/app.component.html"

<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
  <label for="name"
    >Name <input class="form-control" name="name" ngModel required />
  </label>
  <button type="submit">Submit</button>
</form>


<div [hidden]="!itemForm.form.valid">
  <p>{{ submitMessage }}</p>
</div>

当 itemForm 的引用没有 "ngForm" 值时,它将是 HTMLFormElement。不过,组件和指令之间的区别在于,在不指定属性值的情况下组件将引用自身(隐式引用),而指令不会更改隐式引用(仍为所在元素)。

但是,带有 NgForm 时,itemForm 就是对 NgForm 指令的引用,它能够跟踪表单中每个控件的值和有效性。

原生 <form> 元素没有 form 属性,但 NgForm 指令有,这样就能在 itemForm.form.valid 无效的情况下禁用提交按钮,并将整个表单控制树传给父组件的 onSubmit() 方法。

对模板引用变量的思考

模板引用变量(#phone)与模板输入变量(let phone)不同。

模板引用变量的范围是整个模板。因此,不要在同一模板中多次定义相同的变量名,因为它在运行时的值将不可预测。

替代语法 你也可以用 ref- 前缀代替 #。 下面的例子中就用把 fax 变量声明成了 ref-fax 而不是 #fax。

Path:"src/app/app.component.html"

<input ref-fax placeholder="fax number" />
<button (click)="callFax(fax.value)">Fax</button>

输入和输出属性

@Input() 和 @Output() 允许 Angular 在其父上下文和子指令或组件之间共享数据。@Input() 属性是可写的,而 @Output() 属性是可观察对象。

考虑以下父子关系示例:

<parent-component>
  <child-component></child-component>
</parent-component>

在这里,<child-component> 选择器或子指令嵌入在 <parent-component> 中,用作子级上下文。

@Input() 和 @Output() 充当子组件的 API 或应用编程接口,因为它们允许子组件与父组件进行通信。可以把 @Input() 和 @Output() 看做港口或门,@Input() 是进入组件的门,允许数据流入,而 @Output() 是离开组件的门,允许子组件向外发出数据。

关于 @Input() 和 @Output() 这一部分有其自己的现场演练 / 下载范例。以下小节将重点介绍示例应用程序中的关键点。

@Input() 和 @Output() 是独立的

&尽管 @Input() 和 @Output() 通常在应用程序中同时出现,但是你可以单独使用它们。如果嵌套组件只需要向其父级发送数据,则不需要 @Input(),而只需 @Output()。反之亦然,如果子级只需要从父级接收数据,则只需要 @Input()。

如何使用 @Input()

在子组件或指令中使用 @Input() 装饰器,可以让 Angular 知道该组件中的属性可以从其父组件中接收值。这很好记,因为这种数据流是从子组件的角度来看就是输入。因此,@Input() 允许将数据从父组件输入到子组件中。

为了说明 @Input() 的用法,请编辑应用程序的以下部分:

  • 子组件类及其模板

  • 父组件类及其模板

在子组件中

要在子组件类中使用 @Input() 装饰器,请首先导入 Input,然后使用 @Input() 来装饰一个属性:

Path:"src/app/item-detail/item-detail.component.ts"

import { Component, Input } from '@angular/core'; // First, import Input
export class ItemDetailComponent {
  @Input() item: string; // decorate the property with @Input()
}

在这个例子中,@Input() 装饰具有 string 类型的属性 item,但是,@Input() 属性可以具有任何类型,例如 number,string,boolean 或 object。item 的值会来自下一部分要介绍的父组件。

接下来,在子组件模板中,添加以下内容:

Path:"src/app/item-detail/item-detail.component.html"

<p>
  Today's item: {{item}}
</p>

在父组件中

下一步是在父组件的模板中绑定该属性。在此示例中,父组件模板是 "app.component.html"。

首先,使用子组件的选择器(这里是 <app-item-detail> )作为父组件模板中的指令。然后,使用属性绑定将子组件中的属性绑定到父组件中的属性。

Path:"src/app/app.component.html"

<app-item-detail [item]="currentItem"></app-item-detail>

接下来,在父组件类 "app.component.ts" 中,为 currentItem 指定一个值:

Path:"src/app/app.component.ts"

export class AppComponent {
  currentItem = 'Television';
}

借助 @Input(),Angular 将 currentItem 的值传给子级,以便该 item 渲染为 Television。

方括号 [] 中的目标是子组件中带有 @Input() 装饰器的属性。绑定源(等号右边的部分)是父组件要传给内嵌组件的数据。

关键是,当要在父组件中绑定到子组件中的属性(即方括号中的内容)时,必须在子组件中使用 @Input()来装饰该属性。

OnChanges 和 @Input()

&要监视 @Input() 属性的更改,请使用 Angular 的生命周期钩子之一 OnChanges。OnChanges 是专门设计用于具有 @Input() 装饰器的属性的。

如何使用 @Output()

在子组件或指令中使用 @Output() 装饰器,允许数据从子级流出到父级。

通常应将 @Output() 属性初始化为 Angular EventEmitter,并将值作为事件从组件中向外流出。

就像 @Input() 一样,你也要在子组件的属性上使用 @Output(),但其类型为 EventEmitter。

@Output() 将子组件中的属性标记为一扇门,数据可以通过这扇门从子组件传到父组件。 然后,子组件必须引发一个事件,以便父组件知道发生了某些变化。为了引发事件,@Output() 要和 EventEmitter 配合使用,EventEmitter 是 @angular/core 中的一个类,用于发出自定义事件。

要使用 @Output(),请编辑应用程序的以下部分:

  • 子组件类及其模板

  • 父组件类及其模板

下面的示例演示了如何在子组件中设置 @Output(),以将你在 HTML 的 <input> 中输入数据,并将其追加到父组件中的数组里。

在子组件中

此示例有一个 <input>,用户可以在其中输入一个值并单击引发事件的 <button>。然后,通过 EventEmitter 将数据转给父组件。

首先,请确保在子组件类中导入 Output 和 EventEmitter :

import { Output, EventEmitter } from '@angular/core';

接下来,仍然在子组件中,使用组件类中的 @Output() 装饰属性。下面例子中的 @Output() 名叫 newItemEvent,其类型是 EventEmitter,这表示它是一个事件。

Path:"src/app/item-output/item-output.component.ts"

@Output() newItemEvent = new EventEmitter<string>();

上述声明的不同之处如下:

  • @Output() —— 一个装饰器函数,它将该属性标记为把数据从子级传递到父级的一种方式

  • newItemEvent — @Output() 的名字

-EventEmitter<string> — @Output() 的类型

  • new EventEmitter<string>() 告诉 Angular 创建一个新的事件发射器,并且它发射的数据为 string 类型。该类型也可以是任何类型,例如 number,boolean 等。有关 EventEmitter 的更多信息,请参阅 EventEmitter API 文档。

接下来,在同一个组件类中创建一个 addNewItem() 方法:

Path:"src/app/item-output/item-output.component.ts"

export class ItemOutputComponent {


  @Output() newItemEvent = new EventEmitter<string>();


  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}

addNewItem() 函数使用 @Output() newItemEvent 引发一个事件,在该事件中它将发出用户键入到 <input> 中的内容。换句话说,当用户单击 UI 中的 “Add” 按钮时,子组件会让父组件知道该事件,并将该数据传给父组件。

在子组件的模板中

子组件的模板中有两个控件。第一个是带有模板引用变量 #newItem 的 HTML <input>,用户可在其中键入条目名称。用户键入到 <input> 中的内容都存储在 #newItem 变量中。

Path:"src/app/item-output/item-output.component.html"

<label>Add an item: <input #newItem></label>
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>

第二个元素是带有事件绑定的 <button>。之所以知道这是事件绑定,是因为等号的左侧部分在圆括号中 (click)。

(click) 事件绑定到子组件类中的 addNewItem() 方法,无论 #newItem 的值如何,该子组件类均将其作为参数。

现在,子组件已经有了用于将数据发送到父组件的 @Output() 和引发事件的方法。下一步是在父组件中。

在父组件中

在此示例中,父组件是 AppComponent,但是你可以使用任何能嵌套子组件的组件。

此示例中的 AppComponent 具有数组型的 items 列表以及将更多条目添加到数组中的方法。

Path:"src/app/app.component.ts"

export class AppComponent {
  items = ['item1', 'item2', 'item3', 'item4'];


  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

addItem() 方法接收字符串形式的参数,然后将该字符串添加到 items 数组中。

在父组件的模板中

接下来,在父组件的模板中,将父组件的方法绑定到子组件的事件。将子组件选择器(这里是 <app-item-output>)放在父组件的模板 "app.component.html" 中。

Path:"src/app/app.component.html"

<app-item-output (newItemEvent)="addItem($event)"></app-item-output>

事件绑定 (newItemEvent)='addItem($event)'告诉 Angular 将子组件的 newItemEvent 事件连接到父组件中的方法 addItem(),以及将子组件通知父组件的事件作为 addItem() 的参数。换句话说,这是实际传递数据的地方。$event 包含用户在子模板 UI 中键入到 <input> 中的数据。

现在,为了查看 @Output() 工作情况,请将以下内容添加到父组件的模板中:

<ul>
  <li *ngFor="let item of items">
      {{item}}
  </li>
</ul>

*ngFor 会遍历 items 数组中的条目。当你在子组件的 <input> 中输入值并单击按钮时,子组件将发出事件,父组件的 addItem() 方法将值推送到 items 数组,并将其渲染在列表中。

@Input() 和 @Output() 在一起

你可以在和下面代码相同的子组件上使用 @Input() 和 @Output() :

Path:"src/app/app.component.html"

<app-input-output [item]="currentItem" (deleteRequest)="crossOffItem($event)"></app-input-output>

目标 item 是子组件类中的 @Input() 属性,它从父组件的属性 currentItem 中接收值。当你单击删除时,子组件将引发事件 deleteRequest,它携带的值将作为父组件的 crossOffItem() 方法的参数。

下图是同一子组件上的 @Input() 和 @Output(),并显示了每个子组件的不同部分:

如图所示,像分别使用它们那样同时使用输入和输出。在这里,子选择器是 <app-input-output>,其中 item 和 deleteRequest 是子组件类中的 @Input() 和 @Output() 属性。属性 currentItem 和方法 crossOffItem() 都位于父组件类中。

要使用“盒子里的香蕉”语法 [()] 组合属性和事件绑定,请参见双向绑定。

@Input() 和 @Output() 声明

你还可以在指令元数据的 inputs 和 outputs 数组中标出这些成员,而不是使用 @Input() 和 @Output() 装饰器来声明输入和输出,如本例所示:

Path:"src/app/in-the-metadata/in-the-metadata.component.ts"

// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
inputs: ['clearanceItem'],
outputs: ['buyEvent']
// tslint:enable: no-inputs-metadata-property no-outputs-metadata-property

固然可以在 @Directive 和 @Component 元数据中声明 inputs 和 outputs,但最好使用 @Input() 和 @Output() 类修饰符,如下所示:

Path:"src/app/input-output/input-output.component.ts"

@Input() item: string;
@Output() deleteRequest = new EventEmitter<string>();

如果在尝试使用输入或输出时收到了模板解析错误,但是你知道该属性一定存在,请仔细检查你的属性是否使用 @Input() / @Output() 进行了注解,或者是否已在 inputs / outputs 数组中声明了它们:

&
Uncaught Error: Template parse errors:
Can't bind to 'item' since it isn't a known property of 'app-item-detail'

为输入和输出指定别名

有时,输入/输出属性的公共名称应与内部名称不同。虽然最好的方法是避免这种情况,但 Angular 确实提供了一种解决方案。

元数据中的别名

要在元数据中为输入和输出指定别名,请使用冒号分隔(:)的字符串,其左边是属性名,右边是别名:

Path:"src/app/aliasing/aliasing.component.ts" ··· // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property inputs: ['input1: saveForLaterItem'], // propertyName:alias outputs: ['outputEvent1: saveForLaterEvent'] // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property ···

使用 @Input() / @Output() 装饰器指定别名

你可以通过将别名传给 @Input() / @Output() 装饰器来为属性名指定别名。其内部名称保持不变。

Path:"src/app/aliasing/aliasing.component.ts"

@Input('wishListItem') input2: string; //  @Input(alias)
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); //  @Output(alias) propertyName = ...

模板表达式中的运算符

Angular 模板表达式的语言是 JavaScript 语法的子集,并为特定情况添加了一些特殊的运算符。接下来将介绍其中的三个运算符:

  1. 管道

在准备将其用于绑定之前,表达式的结果可能需要进行一些转换。例如,你可以将数字显示为货币,将文本更改为大写,或过滤列表并对其进行排序。

管道是简单的函数,它们接受输入值并返回转换后的值。使用管道运算符(|),很容易在模板表达式中使用它们:

Path:"src/app/app.component.html"

    <p>Title through uppercase pipe: {{title | uppercase}}</p>

管道运算符会把它左侧的表达式结果传给它右侧的管道函数。

还可以通过多个管道串联表达式:

Path:"src/app/app.component.html"

    <!-- convert title to uppercase, then to lowercase -->
    <p>Title through a pipe chain: {{title | uppercase | lowercase}}</p>

你还可以对管道使用参数:

Path:"src/app/app.component.html"

    <!-- pipe with configuration argument => "February 25, 1980" -->
    <p>Manufacture date with date format pipe: {{item.manufactureDate | date:'longDate'}}</p>

json 管道对调试绑定特别有用:

Path:"src/app/app.component.html"

    <p>Item json pipe: {{item | json}}</p>

生成的输出如下所示:

    { "name": "Telephone",
      "manufactureDate": "1980-02-25T05:00:00.000Z",
      "price": 98 }

管道运算符的优先级比三元运算符( ?: )高,这意味着 a ? b : c | x 将被解析为 a ? b : (c | x)。但是,由于多种原因,如果在 ?: 的第一和第二操作数中没有括号,则不能使用管道运算符。一个较好的做法是在第三个操作数中也使用括号。

  1. 安全导航运算符

Angular 安全导航运算符 ? 可以对在属性路径中出现 null 和 undefined 值进行保护。在这里,如果 item 为 null,它可以防止视图渲染失败。

Path:"src/app/app.component.html"

    <p>The item name is: {{item?.name}}</p>

如果 item 为 null,则视图仍然渲染,但显示的值为空白;你只会看到 “The item name is:”,后面没有任何内容。

考虑接下来这个带有 nullItem 的例子。

    The null item name is {{nullItem.name}}

由于没有安全导航运算符,并且 nullItem 为 null,因此 JavaScript 和 Angular 会引发空指针错误并中断 Angular 的渲染过程:

    TypeError: Cannot read property 'name' of null.

但是,有时在某些情况下,属性路径中的 null 值可能是可接受的,尤其是当该值开始时为空但数据最终会到达时。

使用安全导航运算符 ?,当 Angular 表达式遇到第一个空值时,它将停止对表达式的求值,并渲染出无错误的视图。

在像 a?.b?.c?.d 这样的长属性路径中,它工作得很完美。

  1. 非空断言运算符

在 TypeScript 2.0 中,你可以使用 --strictNullChecks 标志强制开启严格空值检查。TypeScript 就会确保不存在意料之外的 null 或 undefined。

在这种模式下,有类型的变量默认是不允许 null 或 undefined 值的,如果有未赋值的变量,或者试图把 null 或 undefined 赋值给不允许为空的变量,类型检查器就会抛出一个错误。

如果无法在运行类型检查器期间确定变量是否 null 或 undefined,则会抛出错误。你可以通过应用后缀非空断言运算符!来告诉类型检查器不要抛出错误。

Angular 的非空断言运算符 ! 在 Angular 模板中具有相同的目的。例如,在使用 *ngIf 检查过 item 是否已定义之后,就可以断言 item 属性也已定义。

Path:"src/app/app.component.html"

    <!-- Assert color is defined, even if according to the `Item` type it could be undefined. -->
    <p>The item's color is: {{item.color!.toUpperCase()}}</p>

当 Angular 编译器把你的模板转换成 TypeScript 代码时,它会防止 TypeScript 不要报告此 item.color 可能为 null 或 undefined 的错误。

与安全导航运算符不同的是,非空断言运算符不会防止出现 null 或 undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检测"。

非空断言运算符 !,是可选的,但在打开严格空检查选项时必须使用它。

内置模板函数

类型转换函数 $any()

有时候,绑定表达式可能会在 AOT 编译时报类型错误,并且它不能或很难指定类型。要消除这种报错,你可以使用 $any() 转换函数来把表达式转换成 any 类型,范例如下:

Path:"src/app/app.component.html"

<p>The item's undeclared best by date is: {{$any(item).bestByDate}}</p>

当 Angular 编译器把模板转换成 TypeScript 代码时,$any 表达式可以防止 TypeScript 编译器在进行类型检查时报错说 bestByDate 不是 item 对象的成员。

$any() 转换函数可以和 this 联合使用,以便访问组件中未声明过的成员。

Path:"src/app/app.component.html"

<p>The item's undeclared best by date is: {{$any(this).bestByDate}}</p>

$any() 转换函数可以用在绑定表达式中任何可以进行方法调用的地方。

模板中的 SVG

可以将 SVG 用作 Angular 中的有效模板。以下所有模板语法均适用于 SVG 和 HTML。在 SVG 1.1和2.0 规范中了解更多信息。

为什么要用 SVG 作为模板,而不是简单地将其作为图像添加到应用程序中?

当你使用 SVG 作为模板时,就可以像 HTML 模板一样使用指令和绑定。这意味着你将能够动态生成交互式图形。

有关语法示例,请参见下面的示例代码片段:

Path:"src/app/svg.component.ts"

import { Component } from '@angular/core';


@Component({
  selector: 'app-svg',
  templateUrl: './svg.component.svg',
  styleUrls: ['./svg.component.css']
})
export class SvgComponent {
  fillColor = 'rgb(255, 0, 0)';


  changeColor() {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    this.fillColor = `rgb(${r}, ${g}, ${b})`;
  }
}

将以下代码添加到你的 svg.component.svg 文件中:

Path:"src/app/svg.component.svg"

<svg>
  <g>
    <rect x="0" y="0" width="100" height="100" [attr.fill]="fillColor" (click)="changeColor()" />
    <text x="120" y="50">click the rectangle to change the fill color</text>
  </g>
</svg>

在这里,你可以看到事件绑定语法 click() 和属性绑定语法([attr.fill]="fillColor")的用法。