Angular4-学习笔记-4-模板语法

学习资料来自 Angular.cnAngular.io

模板语法

在线例子

在 Angular 中,组件扮演着控制器或视图模型的角色,模板则扮演视图的角色。

模板中的 HTML

HTML 是 Angular 模板的语言。

<h1>Hello Angular</h1>

为防范脚本注入攻击的风险,<script> 元素被禁用了(忽略了)。更多内容参见安全

<html><body><base> 元素在此没有使用的意义。

插值表达式 (Interpolation) {{...}}

插值表达式可以把计算后的字符串插入到 HTML 元素标签内的文本或对标签的属性进行赋值。

一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串。

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

模板表达式 (Template expressions)

模板表达式产生一个值。Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。

模板表达式不支持的内容包括:

  • 赋值 (=, +=, -=, ...)
  • new 运算符
  • 使用 ;, 的链式表达式
  • 自增或自减操作符 (++--)
  • 不支持位运算 |&
  • 具有新的模板表达式运算符,比如 |?.
  • 不能引用全局命名空间中的任何东西。 不能引用 windowdocument。不能调用 console.logMath.max

表达式上下文 (Expression context)

典型的表达式上下文是组件实例。
The expression context is typically the component instance.

下例中 titleisUnchanged 均为 AppComponent 的属性。

{{title}}
<span [hidden]="isUnchanged">changed</span>

表达式的上下文可以包括组件之外的对象。 比如模板输入变量 (let hero)和模板引用变量(#heroInput)就是备选的上下文对象之一。

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}

用术语来说,表达式上下文由模版变量,指令上下文对象(如果存在)和组件成员混合而成。如果所引用的名称在上述命名空间中有冲突,那么将会按照模板变量,指令上下文和组件成员的顺序优先选取。

上例展示了一个命名冲突。组件有一个 hero 属性,*ngFor 定义了一个 hero 模版变量。{{hero.name}} 中的 hero 指的是模板变量,而非组件属性。

模板表达式被局限于只能访问来自表达式上下文中的成员。

表达式指南 (Expression guidelines)

模板表达式能成就或毁掉一个应用。应遵循原则:

1. 没有可见的副作用

模板表达式除了目标属性的值以外,不应该改变应用的任何状态。这条规则是 Angular “单向数据流”策略的基础。在一次单独的渲染过程中,视图应该总是稳定的。

2. 执行迅速

Angular 执行模板表达式比我们想象的频繁。当计算代价较高时,应该考虑缓存那些从其它值计算得出的值。

TODO: 如何缓存?

3. 非常简单

模板表达式应保持简单,不要过于复杂。应在组件中实现应用和业务逻辑,使开发和测试变得更容易。

4. 幂等性

使用幂等的表达式没有副作用,并且能提升 Angular 变更检测的性能。
在 Angular 的术语中,幂等的表达式应该总是返回完全相同的东西,直到某个依赖值发生改变。

模板语句 (Template statements)

模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。
A template statement responds to an event raised by a binding target such as an element, component, or directive.

语法:(event)="statement"

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

模板语句有副作用。这是事件的关键所在,可以根据用户的活动更新应用状态。
A template statement has a side effect. That's the whole point of an event. It's how you update application state from user action.

模板语句解析器 (template statement parser) 和模板表达式解析器 (template expression parser) 有所不同,特别之处在于它支持基本赋值 (=) 和表达式链 ( ;, )。

某些 JavaScript 语法仍然是不允许的:

  • new 运算符
  • 自增和自减运算符:++--
  • 操作并赋值,例如 +=-=
  • 位操作符 |&
  • 模板表达式运算符

语句上下文 (Statement context)

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

语句上下文可以引用模板自身上下文中的属性。下例把模板的 $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>

模板语句不能引用全局命名空间的任何东西。比如不能引用 windowdocument,也不能调用 console.logMath.max

语句指南 (Expression guidelines)

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

绑定语法:概览

绑定的类型可以根据数据流的方向分成三类:

  • 从数据源到视图(source-to-view)
  • 从视图到数据源(view-to-source)
  • 双向的从视图到数据源再到视图(view-to-source-to-view)。

单向 source-to-view

语法 绑定类型
{{expression}} 插值表达式
[target]="expression" / bind-target="expression" Property, Attribute 类样式

单向 view-to-source

语法 绑定类型
(target)="statement" / on-target="statement" 事件

双向

语法 绑定类型
[(target)]="expression" / bindon-target="expression" 双向

绑定类型(插值表达式除外)有一个目标名,它位于等号左边,它是 Property 的名字(注意它不是 Attribute 的名字)。

新的思维模型

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。
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>

一旦开始数据绑定,就不再跟 HTML attribute 打交道了。这里不是设置 attribute,而是设置 DOM 元素、组件和指令的 property。

attribute 初始化 DOM property,然后它们的任务就完成了。property 的值可以改变,是当前值;attribute 的值不能改变,是初始值。

即使名字相同,HTML attribute 和 DOM property 也不是同一样东西。

模板绑定是通过 property 和事件来工作的,而不是 attribute。在 Angular 的世界中,attribute 唯一的作用是用来初始化元素和指令的状态。 当进行数据绑定时,只是在与元素和指令的 property 和事件打交道。

绑定目标 (Binding targets)

数据绑定的目标是 DOM 中的某些东西。

Property 绑定类型

目标 范例
Element property <img [src]="heroImageUrl">
Component property <hero-detail [hero]="currentHero"></hero-detail>
Directive property <div [ngClass]="{special: isSpecial}"></div>

事件绑定类型

目标 范例
Element event <button (click)="onSave()">Save</button>
Component event <hero-detail (deleteRequest)="deleteHero()"></hero-detail>
Directive event <div (myClick)="clicked=$event" clickable>click me</div>

双向绑定类型

目标 范例
Event and property <input [(ngModel)]="name">

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 binding) [property]

当要把视图元素的属性 (property) 设置为模板表达式时,就要写模板的属性 (property) 绑定。

最常用的属性绑定是把元素属性设置为组件属性的值。

<img [src]="heroImageUrl">

上例说明:image 元素的 src 属性会被绑定到组件的 heroImageUrl 属性上。

其他示例:

<button [disabled]="isUnchanged">Cancel is disabled</button>

<div [ngClass]="classes">[ngClass] binding to the classes property</div>

<hero-detail [hero]="currentHero"></hero-detail>

单向输入 (One-way in)

人们经常把属性绑定描述成单向数据绑定,因为值的流动是单向的,从组件的数据属性流动到目标元素的属性。

注意:

  • 不能使用属性绑定来从目标元素拉取值
  • 不能绑定到目标元素的属性来读取它。只能设置它。
  • 也不能使用属性 绑定 来调用目标元素上的方法。

如果必须读取目标元素上的属性或调用它的某个方法, 参见 API 参考手册中的 ViewChildContentChild

绑定目标

两种写法:

<img [src]="heroImageUrl">

![](heroImageUrl)

bind- 前缀的写法被称为规范形式 (canonical form)。

元素属性可能是最常见的绑定目标,但 Angular 会先去看这个名字是否是某个已知指令的属性名。

<div [ngClass]="classes">[ngClass] binding to the classes property</div>

严格来说,Angular 正在匹配指令的输入属性的名字。 这个名字是指令的 inputs 数组中所列的名字,或者是带有 @Input() 装饰器的属性。 这些输入属性被映射为指令自己的属性。
Technically, Angular is matching the name to a directive input, one of the property names listed in the directive's inputs array or a property decorated with @Input(). Such inputs map to the directive's own properties.

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

消除副作用

一般建议是,只绑定数据属性和那些只返回值而不做其它事情的方法。

返回恰当的类型

模板表达式应该返回目标属性所需类型的值。

别忘了方括号

一次性字符串初始化

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

  • 目标属性接受字符串值。
  • 字符串是个固定值,可以直接合并到模块中。
  • 这个初始值永不改变。
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>

属性绑定还是插值表达式?

在多数情况下,插值表达式是更方便的备选项。实际上,在渲染视图之前,Angular 把这些插值表达式翻译成相应的属性绑定。

<p>![]({{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>

当要渲染的数据类型是字符串时,基于可读性考虑,建议使用插值表达式。其他情况则必须使用属性绑定。

内容安全

不管是插值表达式还是属性绑定,都不会允许带有 script 标签的 HTML 泄漏到浏览器中。
插值表达式处理 script 标签与属性绑定有所不同,但是二者都只渲染没有危害的内容。

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

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

attribute、class 和 style 绑定

attribute 绑定

语法:[attr.attribute-name]

可以通过 attribute 绑定来直接设置 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
(模板解析错误:不能绑定到 '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>

attribute 绑定的主要用例之一是设置 ARIA attribute

ARIA 指可访问性,用于给残障人士访问互联网提供便利。

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

CSS 类绑定

语法:[class.class-name]

<!-- standard class attribute setting  -->
<div class="bad curly special">Bad curly special</div>

<!-- reset/override all class names with a binding  -->
<div class="bad curly special" [class]="badCurly">Bad curly</div>

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

通常更喜欢使用 ngClass 指令来同时管理多个类名。

样式绑定

语法:[style.style-property]

<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>

带有单位的绑定:

<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>

通常更喜欢使用 ngStyle 指令来同时设置多个内联样式。

事件绑定 (Event binding) (event)

语法:(目标事件)="模版语句"
(target event)="template statement"

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

目标事件

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

<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>

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

$event 和事件处理语句(event handling statements)

在事件绑定中,Angular 会为目标事件设置事件处理器。当事件发生时,这个处理器会执行模板语句。典型的模板语句通常涉及到响应事件执行动作的接收器,例如从 HTML 控件中取得值,并存入模型。
In an event binding, Angular sets up an event handler for the target event. When the event is raised, the handler executes the template statement. The template statement typically involves a receiver, which performs an action in response to the event, such as storing a value from the HTML control into a model.

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

事件对象的形态取决于目标事件。
The shape of the event object is determined by the target event.

如果目标事件是原生 DOM 元素事件, $event 就是 DOM事件对象,它有像 targettarget.value 这样的属性。

<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >

如果事件属于指令,那么 $event 具体是什么由指令决定。

使用 EventEmitter 实现自定义事件

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

示例

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

src/app/hero-detail.component.ts (template)

template: `
<div>
  ![]({{heroImageUrl}})
  <span [style.text-decoration]="lineThrough">
    {{prefix}} {{hero?.name}}
  </span>
  <button (click)="delete()">Delete</button>
</div>`

src/app/hero-detail.component.ts (deleteRequest)

// This component make a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();

delete() {
  this.deleteRequest.emit(this.hero);
}

说明:组件定义了 deleteRequest 属性,它是 EventEmitter 实例。 当用户点击删除时,组件会调用 delete() 方法,让 EventEmitter 发出一个 Hero 对象。

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

<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>

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

模板语句有副作用

模板语句的副作用不仅没问题,反而正是所期望的。

双向数据绑定 (Two-way binding) [(...)]

语法:[(x)]

[(x)] 语法结合了属性绑定的方括号 [x] 和事件绑定的圆括号 (x)。当一个元素拥有可以设置的属性 x 和对应的事件 xChange 时,就可以使用 [(x)] 语法 。

示例 src/app/sizer.component.ts

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.fontSize 被双向绑定到 SizerComponent

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

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

内置指令

内置属性型指令(Built-in attribute directives)

属性型指令会监听和修改其它 HTML 元素或组件的行为、元素 Attribute、DOM Property。 它们通常会作为 HTML Attribute 的名称而应用在元素上。

常见的内置属性型指令:

  • NgClass 添加或移除一组CSS类
  • NgStyle 添加或移除一组CSS样式
  • NgModel 双向绑定到 HTML 表单元素

NgClass

示例:组件方法 setCurrentClasses 可以把组件的属性 currentClasses 设置为一个对象,它将会根据三个其它组件的状态为 truefalse 而添加或移除三个类。

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>

你既可以在初始化时调用 setCurrentClassess(),也可以在所依赖的属性变化时调用。

NgStyle

ngStyle 需要绑定到一个 key:value 控制对象。 对象的每个 key 是样式名,它的 value 是能用于这个样式的任何值。

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'
  };
}

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

NgModel

要使用 ngModel 需要导入 FormsModule 模块。

示例:

import { NgModule } from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular

/* Other imports */

@NgModule({
  imports: [
    BrowserModule,
    FormsModule  // <--- import into the NgModule
  ],
  /* Other module metadata */
})
export class AppModule { }

更多关于 FormsModulengModel 的内容参见表单

使用 ngModel 实现双向数据绑定。

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

该语句实际上隐藏了其实现细节:

<input [ngModel]="currentHero.name"
       (ngModelChange)="currentHero.name=$event">

如果需要做一些不同的处理,就不能使用 [(ngModel)] 语法,而应写成扩展的方式:

<input [ngModel]="currentHero.name"
       (ngModelChange)="setUppercaseName($event)">

ngModel 指令只能用在支持 ControlValueAccessor 的元素上。

内置结构型指令(Built-in structural directives)

结构型指令负责 HTML 布局。

常见的内置结构型指令:

  • NgIf conditionally add or remove an element from the DOM
  • NgFor repeat a template for each item in a list
  • NgSwitch a set of directives that switch among alternative views

NgIf

You can add or remove an element from the DOM by applying an NgIf directive to that element (called the host elment).

示例:

<hero-detail *ngIf="isActive"></hero-detail>

When the isActive expression returns a truthy value, NgIf adds the HeroDetailComponent to the DOM. When the expression is falsy, NgIf removes the HeroDetailComponent from the DOM, destroying that component and all of its sub-components.

别忘了 ngIf 前面的星号( * )。

这和显示/隐藏不是一回事。

我们也可以通过类绑定或样式绑定来显示或隐藏一个元素。但隐藏子树和用 NgIf 排除子树是截然不同的。

当隐藏子树时,它仍然留在 DOM 中。当 NgIffalse 时 Angular 从 DOM 中物理地移除了子树,它销毁了子树中的组件及其状态,也潜在释放了可观的资源。

防范空指针错误

NgIf 指令通常会用来防范空指针错误。 而显示/隐藏的方式是无法防范的。

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

currentHero 的名字只有当存在 currentHero 时才会显示出来。 而 nullHero 永远不会显示。

NgFor

NgFor 是一个重复器指令——显示列表项的一种方式。你先定义了一个 HTML 块,它规定了单个条目应该如何显示。然后你告诉 Angular 把这个块当做模板,渲染列表中的每个条目。
NgFor is a repeater directive — a way to present a list of items. You define a block of HTML that defines how a single item should be displayed. You tell Angular to use that block as a template for rendering each item in the list.

例子1:

<div *ngFor="let hero of heroes">{{hero.name}}</div>

例子2:

<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>

别忘了 ngFor 前面的星号( * )。

赋值给 *ngFor 的字符串不是模板表达式,它是一个微语法 —— 由 Angular 自己解释的小型语言。

字符串 "let hero of heroes" 的含义:

取出 heroes 数组中的每个英雄,把它存入局部变量 hero 中,并在每次迭代时对模板 HTML 可用。
Take each hero in the heroes array, store it in the local hero looping variable, and make it available to the templated HTML for each iteration.

模板输入变量 (Template input variables)

The let keyword before hero creates a template input variable called hero.

*ngFor 的索引 (index)

The index property of the NgFor directive context returns the zero-based index of the item in each iteration.

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

更多内容参见 NgFor API

*ngFor with trackBy

ngFor 指令有时候会性能较差,特别是在大型列表中。对一个条目的一丁点改动、移除或添加,都会导致级联的 DOM 操作。

有了 trackBy,则只有 id 发生改变才会触发元素替换。

在组件中添加方法:

trackByHeroes(index: number, hero: Hero): number { return hero.id; }

使用 trackBy

<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
  ({{hero.id}}) {{hero.name}}
</div>

NgSwitch

NgSwitch can display one element from among several possible elements, based on a switch condition.

NgSwitch 由三个指令组成:

  • 属性型指令 NgSwitch
  • 结构型指令 NgSwitchCase
  • 结构型指令 NgSwitchDefault

示例:

<div [ngSwitch]="currentHero.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="currentHero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="currentHero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="currentHero"></unknown-hero>
</div>

NgSwitch 指令在增加或移除组件元素 (component elements) 时尤其有用。

模板引用变量 (Template reference variables) #var

模板引用变量通常是一个模版中的对 DOM 元素的一个引用。
A template reference variable is often a reference to a DOM element within a template.

使用井号 # (或 ref-)来声明一个模板引用变量。The#phone declares a phone variable on an <input> element.

<input #phone placeholder="phone number">
或者写成
<input ref-phone placeholder="phone number">

你可以在模板中的任意位置引用该模板引用变量。

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

说明:phone refers to the phone number <input> box. The phone button click handler passes the *input *value to the component's callPhone method.

模板引用变量如何获取自身的值?

通常,如果一个元素声明了一个模板引用变量,那么 Angular 会将模板引用变量的值设置为该元素的值。
In most cases, Angular sets the reference variable's value to the element on which it was declared.

示例:

<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
  <div class="form-group">
    <label for="name">Name
      <input class="form-control" name="name" required [(ngModel)]="hero.name">
    </label>
  </div>
  <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
  {{submitMessage}}
</div>

If Angular hadn't taken it over when you imported the FormsModule, it would be the HTMLFormElement. The heroForm
is actually a reference to an Angular NgForm directive with the ability to track the value and validity of every control in the form.

The native <form> element doesn't have a form property. But the NgForm directive does, which explains how you can disable the submit button if the heroForm.form.valid is invalid and pass the entire form control tree to the parent component's onSubmit method.

注意

模板引用变量 (template reference variable) (#phone) 与模板输入变量 (template input variable) (*ngFor 中的 let phone) 并不相同。详见结构型指令

输入输出属性 @Input@Output

绑定目标与绑定源的区别:

  • 绑定的目标是在 = 左侧的部分, 则是在 = 右侧的部分。
  • 绑定的目标是绑定符:[]()[()] 中的属性或事件名, 源则是引号 " " 中的部分或插值符号 {{}} 中的部分。
  • 指令中的每个成员都会自动在绑定中可用。 不需要特别做什么,就能在模板表达式或语句中访问指令的成员。
  • 访问目标指令中的成员则受到限制。 只能绑定到那些显式标记为输入输出的属性。

iconUrlonSave 是绑定源

<img [src]="iconUrl"/>
<button (click)="onSave()">Save</button>

HeroDetailComponent.hero 是属性绑定的目标。 HeroDetailComponent.deleteRequest 是事件绑定的目标。

<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>

声明输入和输出属性

目标属性必须被显式的标记为输入或输出。

方法1:使用装饰器 @Input()@Output()

@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();

方法2:通过元数据数组。

@Component({
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})

两种方法不可同时使用。

输入还是输出?

输入属性通常接收数据值。 输出属性暴露事件生产者。
Input properties usually receive data values. Output properties expose event producers.

输入和输出这两个词是从目标指令的角度来说的。

  • HeroDetailComponent 角度来看,HeroDetailComponent.hero 是个输入属性, 因为数据流从模板绑定表达式流入那个属性。
  • HeroDetailComponent 角度来看,HeroDetailComponent.deleteRequest 是个输出属性, 因为事件从那个属性流出,流向模板绑定语句中的处理器。

给输入输出属性起别名

方法1:把别名传进 @Input / @Output 装饰器,就可以为属性指定别名:

@Output('myClick') clicks = new EventEmitter<string>(); //  @Output(alias) propertyName = ...

方法2:在 inputsoutputs 数组中为属性指定别名。 语法(属性名:别名)。

@Directive({
  outputs: ['clicks:myClick']  // propertyName:alias
})

模板表达式操作符

管道操作符 |

管道是一个简单的函数,它接受一个输入值,并返回转换结果。

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

输出结果:

{ "id": 0, "name": "Hercules", "emotion": "happy",
  "birthdate": "1970-02-25T08:00:00.000Z",
  "url": "http://www.imdb.com/title/tt0065832/",
  "rate": 325 }

安全导航操作符 ( ?. ) 和空属性路径

Angular 的安全导航操作符 (?.) 用来保护出现在属性路径中 null 和 undefined 值。示例:

The current hero's name is {{currentHero?.name}}

说明:当 currentHero 为空时,保护视图渲染器,让它免于失败。

显示一个空 (null) 英雄的 name 示例:

The null hero's name is {{nullHero.name}}

:marked
  JavaScript throws a null reference error, and so does Angular:
JavaScript 抛出了空引用错误,Angular 也是如此:code-example(format="nocode").
  TypeError: Cannot read property 'name' of null in [null].

currentHero 为空的时候,应用崩溃了,整个视图都不见了。

笨重的解决办法1:

<!--No hero, div not displayed, no error -->
<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>

笨重的解决办法2:

The null hero's name is {{nullHero && nullHero.name}}

正确的解决办法:

<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.name}}

总结

透彻理解模板语法对开发至关重要。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 数据绑定为应用程序提供了一种简单一致的机制,来管理与协调数据的显示,以及数据值的变化。angular提供了多种数据...
    oWSQo阅读 668评论 0 1
  • 本系列教程的主要内容来源于 egghead.io get-started-with-angular 视频教程,但针...
    semlinker阅读 10,736评论 1 21
  • 几天前,我把电脑的F盘格式化,删除了《绝命毒师》、《绝命律师》、《汉尼拔》、《别对我撒谎》这些在我成长阶段留下快乐...
    鬼纹阅读 208评论 0 0
  • S1无论多远未来 读来依然一字一句一篇都灿烂 小夏是一个每天都能乐得屁颠屁颠的女孩,用同学的话来说就是没心没肺活着...
    西洲玖尧阅读 675评论 0 2