# 模板语法
使用模型-视图-控制器 (MVC) 概念来理解 Angular,组件扮演着控制器或视图模型的角色,模板则扮演视图的角色。
## HTML
几乎所有的 HTML 语法都是有效的模板语法。但 `Syntax';
```
幸运的是,Angular 数据绑定对危险的 HTML 早有防备。在显示它们之前,会先对内容进行无害化处理。
## HTML 属性、class 和 style 绑定
模板语法为那些不太适合使用属性绑定的场景提供了专门的单向数据绑定形式。
### Attribute 绑定
我们可以通过 Attribute 绑定 来直接设置 Attribute 的值。
ARIA, SVG 和 table 中的 `colspan/rowspan` 等 Attribute。它们是纯粹的 Attribute,没有对应的属性可供绑定。
```html
One-Two |
```
### CSS 类绑定
借助 CSS 类绑定,我们可以从元素的 class Attribute 上添加和移除 CSS 类名。
CSS 类绑定在语法上类似于属性绑定,完整格式为 `[class.class-name]` 其中后面的`.class-name` 是可选的。
```html
Bad curly special
Bad curly
This one is not so special
```
> 虽然这是一个切换单一类名的好办法,但我们通常更喜欢使用 `NgClass` 指令来同时管理多个类名。
### 样式绑定
通过 样式绑定 ,我们可以设置内联样式。完整格式为 `[style.style-property]` 其中后面的 `.style-property` 是可选的。
```html
```
> 虽然这是一个设置单一样式的好办法,但我们通常更喜欢使用 NgStyle 指令 来同时设置多个内联样式。
>
> 注意,一个 样式属性 命名方法可以用 **中线命名法**,像上面的一样 也可以用 **驼峰式命名法**,比如 fontSize。
## 事件绑定
事件绑定语法由等号左侧带圆括号的 **目标事件**,和右侧一个引号中的 **模板语句** 组成。下列事件绑定监听按钮的点击事件。无论什么时候,发生点击时,都会调用组件的 onSave() 方法。
```html
```
### 目标事件
圆括号中的名称 ——如 (click) ——标记出了目标事件。
**元素事件** 可能是更常见的目标,但 Angular 会先看这个名字是否能匹配上已知指令的事件属性。
如果这个名字没能匹配到元素事件或已知指令的输出型属性,Angular 就会报“未知指令”错误。
### `$event` 和事件处理语句
在事件绑定中,Angular 会为目标事件设置事件处理器。当事件发生时,这个处理器会执行模板语句。
事件绑定会通过一个 **名叫 $event 的事件对象** 传达关于此事件的信息(包括数据值)。
事件对象的形态取决于目标事件。如果目标事件是一个原生 DOM 元素事件, $event 就是一个 DOM 事件对象 ,它有像 target 和 target.value 这样的属性。
如果这个事件属于指令(回想一下:组件是指令的一种),那么 $event 便具有指令中生成的形态。
### 使用 EventEmitter 实现自定义事件
指令使用典型的 Angular `EventEmitter` 来触发自定义事件。指令调用 `EventEmitter.emit(payload)` 来触发事件,传进去的消息载荷可以是任何东西。父指令通过绑定到这个属性来监听这个事件,并且通过 $event 对象来访问这个载荷。
```ts
// HeroDetailComponent.ts
template: ``
// ...
@Output() deleteRequest = new EventEmitter();
delete() { this.deleteRequest.emit(this.hero); }
```
### 模板语句有副作用
deleteHero 方法有一个副作用:它删除了一个英雄。模板语句的副作用不仅没问题,反而正是我们所期待的。
## 双向数据绑定
> 要使用 ngModel 做双向数据绑定,得先把 FormsModule 导入我们的模块并把它加入 NgModule 装饰器的 imports 数组。
### `[(ngModel)]` 内幕
```html
```
Angular 会把 `[(x)]` 语法去掉语法糖,变成了一个供属性绑定用的输入属性 x ,和一个供事件绑定用的输出属性 xChange。Angular 通过在模板表达式的原始字符串后面追加上 =$event,来构建出供事件绑定用的模板语句。
利用这一行为,我们也可以自己写出具有双向绑定功能的指令。
```ts
[(x)]="e" <==> [x]="e" (xChange)="e=$event"
```
## 内置指令
前一个版本的 Angular 中包含了超过 70 个内置指令。社区贡献了更多,这还没算为内部应用而创建的无数私有指令。
在 Angular2 中我们不需要那么多指令。使用更强大、更富有表现力的 Angular 绑定系统,我们其实可以达到同样的效果。
我们仍然可以从简化复杂任务的指令中获益。Angular 发布时仍然带有内置指令,只是没那么多了。我们仍会写自己的指令,只是没那么多了。下面这部分就检阅一下那些最常用的内置指令。
### `NgClass`
CSS 类绑定 是添加或删除 单个 类的最佳途径。当我们想要同时添加或移除 多个 CSS 类时,NgClass 指令可能是更好的选择。
```html
This div is saveable and special
```
### `NgStyle`
我们可以基于组件的状态动态设置内联样式。 绑定到 NgStyle 可以让我们同时设置很多内联样式。
样式绑定 是设置 单一 样式值的简单方式,如果我们要同时设置 多个 内联样式, NgStyle 指令可能是更好的选择。
```html
...
```
### `NgIf`
通过把 NgIf 指令绑定到一个真值表达式,我们可以把一个元素的子树 ( 元素及其子元素 ) 添加到 DOM 上。
```html
Hello, {{currentHero.firstName}}
```
#### 可见性和 NgIf 不是一回事
我们可以通过 类绑定 或 样式绑定 来显示和隐藏一个元素的子树(元素及其子元素)。隐藏一个子树和用 NgIf 排除一个子树是截然不同的。
当我们隐藏一个子树时,它仍然留在 DOM 中。子树中的组件及其状态仍然保留着。即使对于不可见属性,Angular 也会继续检查变更。子树可能占用相当可观的内存和运算资源。
当 NgIf 为 false 时,Angular 从 DOM 中实际移除了这个元素的子树。它销毁了子树中的组件及其状态,也潜在释放了可观的资源,最终让用户体验到更好的性能。
### `NgSwitch`
当需要从 **一组** 可能的元素树中根据条件显示 **一个** 时,我们就把它绑定到 `NgSwitch`。Angular 将只把选中的元素树放进 DOM 中。
```html
Eenie
Meanie
other
```
### `NgFor`
```html
{{hero.fullName}}
```
赋值给 `*ngFor` 的字符串并不是一个 **模板表达式**,它是一个 **微语法** ——由 Angular 自己解释的小型语言。
`ngFor` 指令支持一个可选的 `index` ,它在迭代过程中会从 0 增长到“当前数组的长度”。
```
{{i + 1}} - {{hero.fullName}}
```
`ngFor` 指令有时候会性能较差,特别是在大型列表中。对一个条目的一点小更改、移除或添加,都会导致级联的 DOM 操作。
如果我们给它一个 **追踪** 函数,Angular 就可以避免这种折腾。追踪函数告诉 Angular:我们知道两个具有相同 hero.id 的对象其实是同一个英雄。
```ts
trackByHeroes(index: number, hero: Hero) { return hero.id; }
```
```html
({{hero.id}}) {{hero.fullName}}
```
## `*` 与 ``
`*` 是一种语法糖,它让那些需要借助模板来修改 HTML 布局的指令更易于读写。NgFor、NgIf 和 NgSwitch 都会添加或移除元素子树,这些元素子树被包裹在 `` 标签中。
我们没有看到 `` 标签,那是因为这种 `*` 前缀语法让我们忽略了这个标签,而把注意力直接聚焦在所要包含、排除或重复的那些 HTML 元素上。
```html
```
This allows you to easily create an embedded template, which would turn into this:
```html
```
### 展开 `*ngIf`
```html
```
```html
```
### 展开 `*ngSwitch`
```html
Eenie
Meanie
other
Eenie
Meanie
other
```
### 展开 `*ngFor`
```html
```
```html
```
## 模板引用变量
模板引用变量 是模板中对 DOM 元素或指令的引用。它能在原生 DOM 元素中使用,也能用于 Angular 组件。
```html
```
```html
```
## 输入输出属性
我们要重点突出下绑定 **目标** 和绑定 **源** 的区别。
绑定的 **目标** 是在 `=` 左侧的部分,**源** 则是在 `=` 右侧的部分。
绑定的 **目标** 是绑定符:`[]`、`()` 或 `[()]` 中的属性或事件名,**源** 则是引号 `" "` 中的部分或插值符号 `{{ }}` 中的部分。
**源** 指令中的每个成员都会自动在绑定中可用。我们不需要特别做什么,就能在模板表达式或语句中访问指令的成员。
访问 **目标** 指令中的成员则 **受到限制**。我们只能绑定到那些显式标记为 **输入** 或 **输出** 的属性。
### 声明输入和输出属性
我们既可以通过装饰器,又可以通过元数据数组来指定输入 / 输出属性。但别同时用!
```ts
@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter();
```
```ts
@Component({
inputs: ['hero'],
outputs: ['deleteRequest'],
})
```
### 输入 / 输出属性别名
```html
click with myClick
```
```ts
@Output('myClick') clicks = new EventEmitter(); // @Output(alias) propertyName = ...
@Directive({
outputs: ['clicks:myClick'] // propertyName:alias
})
```
## 模板表达式操作符
模板表达式语言使用了 JavaScript 语法的一个子集,并补充了几个用于特定场景的特殊操作符。这里我们讲其中的两个:**管道** 和 **安全导航操作符**。
### 管道操作符 `|`
管道是一个简单的函数,它接受一个输入值,并返回转换结果。它们很容易用于模板表达式中。
```html
Title through uppercase pipe: {{title | uppercase}}
Title through a pipe chain: {{title | uppercase | lowercase}}
Birthdate: {{currentHero?.birthdate | date:'longDate'}}
{{currentHero | json}}
```
### 安全导航操作符 ( ?. ) 和空属性路径
Angular 的 安全导航操作符 (?.) 是一种便利的方式,当属性路径中出现 null 和 undefined 值,保护视图渲染器,让它免于失败。
```
The current hero's name is {{currentHero.firstName}}
// 当 currentHero 为定义时会抛出错误:
// TypeError: Cannot read property 'firstName' of null in [null]
The null hero's name is {{nullHero?.firstName}} // 加了安全导航操作符后就不会报错
```