Angular 5.x Template Syntax Learn Note
Angular 5.x 模板语法学习笔记
标签(空格分隔): Angular
上手
官网Heroes 例子:Demo On Github。
一、模板与数据绑定
1. 显示数据
selector
选择自定义标签的名称。-
template
属性定义内联模板;templateUrl
属性定义外链模板。 -
值的声明和初始化。
export class AppComponent {
// 变量赋值的方式初始化组件
// title = 'Tour of Heroes';
// myHero = 'Vencent';
title: string;
myHero: string;
Heroes = ['Windstorm', 'Vencent', 'Bombasto', 'Magneta', 'Tornado',];
// 构造函数来声明和初始化属性
constructor() {
this.title = 'Tour of Heroes';
this.myHero = this.Heroes[1];
}
} -
{{ [data] }}
插值表达式 (interpolation) ,模板绑定属性;*ngFor="let [item] of [list]"
模板循环,ngFor
可以为任何可迭代的 (iterable) 对象重复渲染条目;ngIf
指令会根据一个布尔条件来显示或移除一个元素,*ngIf="[condition]"
。具体参见下一节(模板语法)。 -
实体类的声明。
export class Hero {
constructor(
public id: number,
public name: string) { }
}public id: number,
- 声明了一个构造函数参数及其类型
- 声明了一个同名的公共属性
-
new
出该类的一个实例时,把该属性初始化为响应的参数值
2. 模板语法
HTML是Angular模板的语言
HTML是Angular模板的语言;<script>
元素,它被禁用(忽略)了,以阻止脚本注入攻击的风险:安全。-
插值表达式
{{...}}
// 作为文本
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>// 作为属性值 image = img,由于此md编辑器会吞掉img标签
<image src="{{heroImageUrl}}" style="height:30px"> -
模板表达式
JavaScript 中那些具有或可能引发副作用的表达式是被禁止的:- 赋值(
=
,+=
,-=
,...) -
new
运算符 - 使用
;
或,
的链式表达式 - 自增或自减操作符(
++
和--
) - 不支持位运算
|
和&
- 具有新的模板表达式运算符,比如
|
、?.
和!
3.1 表达式上下文
典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。
表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果我们要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。 - 赋值(
-
模板语句
模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。
模板语句解析器支持基本赋值 (<button (click)="deleteHero()">Delete hero</button>
=
) 和表达式链 (;
和,
)。
模板语句禁止以下:-
new
运算符 - 自增或自减操作符(
++
和--
) - 操作并赋值(
+=
,-=
,...) - 位操作符
|
和&
-
模板表达式运算符,比如
|
、?.
和!
4.1 语句上下文
典型的语句上下文就是当前组件的实例。
<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
。 -
-
绑定语法
数据方向 语法 绑定类型 单向
从数据源
到视图目标{{expression}}
[target]="expression"
bind-target="expression"
插值表达式
Property
Attribute
类
样式单向
从视图目标
到数据源(target)="statement"
on-target="statement"
事件 双向 [(target)]="expression"
bindon-target="expression"
双向 -
HTML attribute value
指定了初始值;DOM value property
是当前值。 - 模板绑定是通过
property
和事件来工作的,而不是attribute
。 - 插值表达式和属性绑定只能设置属性,不能设置 attribute。
- 在 Angular 的世界中,attribute 唯一的作用是用来初始化元素和指令的状态。
如图 5-1 所示,创建了一个
input
元素,初始设置它的属性(Attribute)为 "Bob",在获取它的 Attribute 和 使用ng双向绑定值时,用户输入值(123)和 Attribute 的值(Bob)是不一样的。5.1 属性绑定 ( [属性名] )
单向输入
-
绑定目标
<img [src]="heroImageUrl">
-
消除副作用
只绑定数据属性和那些只返回值而不做其它事情的方法。
-
返回恰当的类型
模板表达式应该返回目标属性所需类型的值。如果目标属性想要个数字,就返回数字。HeroDetail组件的hero属性想要一个Hero对象,那就在属性绑定中精确地给它一个Hero对象:
<app-hero-detail [hero]="currentHero"></app-hero-detail>
-
别忘了方括号
方括号告诉 Angular 要计算模板表达式。
-
一次性字符串初始化
- 目标属性接受字符串值。
- 字符串是个固定值,可以直接合并到模块中。
- 这个初始值永不改变。
下面这个例子把HeroDetailComponent的prefix属性初始化为固定的字符串,而不是模板表达式。
<app-hero-detail prefix="You are my" [hero]="currentHero"></app-hero-detail>
-
属性绑定还是插值表达式?
<p><span>"{{titles}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="titles"></span>" is the <i>property bound</i> title.</p>倾向于插值表达式
但数据类型不是字符串时,就必须使用属性绑定了,如图 5-2 所示: -
内容安全
不管是插值表达式还是属性绑定,都不会允许带有 script 标签的 HTML 泄漏到浏览器中。只渲染没有危害的内容。比如下面的绑定:
<p [innerHTML]="scripts"></p>
5.2 attribute、class 和 style 绑定
-
attribute 绑定
这是“绑定到目标属性 (property)”这条规则中唯一的例外。这是唯一的能创建和设置 attribute 的绑定形式。
由attr前缀,一个点 (.) 和 attribute 的名字组成。
把[attr.colspan]绑定到一个计算值:<table border="1">
<!-- 设置 colspan=2 -->
<tr><td [attr.colspan]="1 + 1">1-2</td></tr>
<tr><td>5</td><td>6</td></tr>
</table>运行结果:
attribute 绑定的主要用例之一是设置 ARIA attribute(译注:ARIA指可访问性,用于给残障人士访问互联网提供便利):
<!-- 创建和设置辅助技术的 ARIA 属性 -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button> -
CSS 类绑定
由class前缀,一个点 (.)和 CSS 类的名字组成,
[class.class-name]
。<!-- 使用绑定重置/覆盖所有类名 -->
<div class="bad curly special" [class]="badCurly">Bad curly</div>
可以绑定到特定的类名。<!-- 用属性打开/关闭“special”类 -->
<div [class.special]="isSpecial">The class binding is special</div>
<!-- 绑定`class.special`优先级高于class属性 -->
<div class="special" [class.special]="!isSpecial">This one is not so special</div> -
样式绑定
设置内联样式。由style前缀,一个点 (.)和 CSS 样式的属性名组成,
[style.style-property]
。<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
有些样式绑定中的样式带有单位:
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
5.3 事件绑定 ( (事件名) )
事件绑定语法由等号左侧带圆括号的目标事件和右侧引号中的模板语句组成。
<button (click)="onSave()">Save</button>
-
$event 和事件处理语句
绑定会通过名叫$event的事件对象传递关于此事件的信息(包括数据值)。
<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value">执行效果:
-
指令使用 Angular EventEmitter 来触发自定义事件。
指令创建一个EventEmitter实例,并且把它作为属性暴露出来。
指令调用EventEmitter.emit(payload)
来触发事件,可以传入任何东西作为消息载荷。
父指令通过绑定到这个属性来监听事件,并通过$event
对象来访问载荷。
5.4 双向数据绑定 ( [(...)] )
[(x)]
语法结合了属性绑定的方括号[x]
和事件绑定的圆括号(x)
。sizer.component.ts
:import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-sizer',
templateUrl: './sizer.component.html'
})
export class SizerComponent {
@Input() size: number | string;
@Output() sizeChange = new EventEmitter<number>();
dec() { this.resize(-1); }
inc() { this.resize(+1); }
resize(delta: number) {
console.log(delta, +this.size)
this.size = Math.min(40, Math.max(8, +this.size + delta));
console.log(this.size)
this.sizeChange.emit(this.size);
}
}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>app.component.ts
:<app-sizer [(size)]="fontSizePx"></app-sizer>
<!-- 等价于下面的写法 -->
<!-- <app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer> -->
<div [style.font-size.px]="fontSizePx">Resizable Text</div>app.component.ts
:fontSizePx: number = 12;
同时注意在
app.module.ts
引入sizer.component.ts
。
运行结果如图 5-9 所示: -
-
内置指令
分为属性型指令和结构型指令。
6.1 常用内置属性型指令
属性型指令会监听和修改其它HTML元素或组件的行为、元素属性(Attribute)、DOM属性(Property)。
它们通常会作为HTML属性的名称而应用在元素上。- NgClass - 添加或移除一组CSS类
- NgStyle - 添加或移除一组CSS样式
- NgModel - 双向绑定到HTML表单元素
-
NgClass
指令可以同时添加或移除多个类。
CSS 类绑定 是添加删除单个类的最佳途径。
而同时添加或移除多个 CSS 类,更好的选择可能是NgClass
。// NgClass
canSave: boolean = true;
isUnchanged: boolean = false;
isSpecial: boolean = true;
currentClasses: {};
setCurrentClasses() {
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
}
}
ngOnInit(): void {
// 初始化 currentClasses
this.setCurrentClasses();
}把
NgClass
属性绑定到currentClasses
,根据它来设置此元素的CSS类:<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
运行结果如图 6-1所示:
-
NgStyle
指令NgStyle
绑定可以同时设置多个内联样式。
同样的,样式绑定也是设置单一样式值的简单方式。NgStyle
是同时设置多个内联样式更好的选择。// ngStyle
currentStyles: {};
setCurrentStyles() {
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 by ngStyle.
</div>运行结果如图 6-2所示:
-
NgModel
- 使用[(ngModel)]双向绑定到表单元素双向绑定表单元素。
需要引入FormModule
。import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
FormsModule
]
})
export class AppModule { }ngModel
可以通过之前的属性绑定和事件绑定实现:<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value">ngModel
的展开实现(自己的输入(ngModel
)属性和输出(ngModelChange
)属性):<input [ngModel]="currentHero.name"
(ngModelChange)="currentHero.name=$event">ngModel
的合并实现方法是[(ngModel)]
:<input [(ngModel)]="currentHero.name">
[(ngModel)]
只能设置数据绑定属性,如果有特别需求也可以展开形式:app.component.html
:<input [ngModel]="currentHero.name"
(ngModelChange)="setUppercaseName($event)">app.component.ts
:setUppercaseName(name) {
this.currentHero.name = name.toUpperCase();
}执行效果如下图(图 6-3)所示:
6.2 常用内置结构型指令
结构型指令的职责是HTML布局。添加、移除和操纵DOM元素。
注意指令前面的*
号。- NgIf - 根据条件把一个元素添加到DOM中或从DOM移除
- NgSwitch - 一组在替代视图之间切换的指令
- NgForOf - 对列表中的每个条目重复套用一个模板
-
NgIf
指令和vue的
v-if
,v-show
一样,不同于隐藏元素。ngIf
可以防范空指针错误<div *ngIf="currentHero">Hello, {{ currentHero.name }}</div>
<div *ngIf="nullHero">Hello, {{ nullHero.name }}</div> -
NgFor
指令重复器指令。可以用在简单的元素上,也可以用在一个组件元素上。
赋值给*ngFor
的文本是用于指导重复器如何工作的指令 - 微语法(microsyntax)。<div *ngFor="let hero of heroes; let i = index">{{ i }} - {{hero.id + ': ' + hero.name }}</div>
trackBy
可以防止渲染全部条目。trackByHeroes(index: number, hero: Hero): number { return hero.id; }
<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
({{hero.id}}) {{hero.name}}
</div>如图 6-4 所示,如果没有trackBy,这些按钮都会触发完全的DOM元素替换;有了trackBy,则只有修改了id的按钮才会触发元素替换。
-
NgSwitch
指令可以从多个可能的元素中根据switch条件来显示某一个。包括三个相互协作指令:
NgSwitch
、NgSwitchCase
、NgSwitchDefault
。Pick ur favorite hero:
<div class="favorite-hero">
<label *ngFor="let hero of heroes">
<input type="radio" name="hero" value="{{hero}}" (change)="selectHero(hero)"> {{hero.name}}
</label>
<div class="hero-desc" *ngIf="selectedHero" [ngSwitch]="selectedHero.id">
<p *ngSwitchCase="1">Wow. U like {{ selectedHero.name }}, let's play!</p>
<p *ngSwitchCase="2">Hey man, this is {{ selectedHero.name }}, fk u!</p>
<p *ngSwitchCase="3">U like {{ selectedHero.name }}? R u sure?</p>
<p *ngSwitchCase="4">Let's fk {{ selectedHero.name }}, go go go!</p>
<p *ngSwitchDefault>Little {{ selectedHero.name }} is a cat.</p>
</div>
</div>selectedHero: Hero;
selectHero(hero: Hero): void {
this.selectedHero = hero;
}运行效果如图 6-5 所示:
-
模板引用变量(#var)
模板引用变量通常用来引用变量中的某个DOM元素,它还可以引用Angular组件或指令或Web Component。
使用井号(#)来声明引用变量。#phone
的意思就是声明一个名叫phone
的变量来引用<input>
元素。
通俗来讲,#[name]
表示的是DOM元素,而在DOM其他地方可以直接调用这个name
,取到的是DOM元素。比如下面的例子:
<input type="text" #phone placeholder="phone number" [(ngModel)]="phoneNum">
<button type="button" name="button" (click)="callPhone(phone.value)">console.log phone</button>phoneNum: string = '15342018244';
callPhone(phone: string): void {
console.log(phone);
}运行效果如图 7-1 所示。
指令也可以修改这种行为,让这个值引用到别处,比如它自身。
NgForm
指令就是这么做的。<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
<!-- 这里的heroForm实际上是一个Angular NgForm 指令的引用, 因此具备了跟踪表单中的每个控件的值和有效性的能力。 -->
<div class="form-group">
<label for="name">Name
<input type="text" name="name" required [(ngModel)]="submitHero.name">
</label>
</div>
<button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
{{submitMessage}}
</div>submitHero: Hero = new Hero(null, '');
submitMessage: string;
onSubmit(hero): void {
console.log(hero.form.value);
this.submitMessage = 'success!';
}运行效果如图 7-2 所示:
可以用
ref-
前缀代替#
。<input ref-fax placeholder="number">
<button (click)="callFax(fax.value)">BTN</button> -
输入输出属性(@Input和@Output)
所有组件皆为指令
绑定目标
等号左侧、绑定符:()
,[]
,[()]
中的属性/事件名、只能绑定到显示标记为输入或输出属性。
绑定源
等号右侧、引号""
或插值符号{{}}
中的部分、源指令中的每个成员都会自动在绑定中可用。8.1 声明输入和输出属性
目标属性必须被显示的标记为输入或输出。
<app-hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</app-hero-detail>src/app/hero-detail.component.ts
:@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();还可以在指令元数据的
inputs
或outputs
数组中标记处这些成员。@Component({
inputs: ['hero'],
outputs: ['deleteRequest']
});但是两者不能同时使用。
8.2 输入输出选择
输入属性:接收数据值;
输出属性:暴露时间生产者。
输入和输出两个词是从目标指令角度说的。8.3 给输入/输出属性起别名
把别名传进@Input/@Output装饰器,就可以为属性指定别名:
@Output('myClick') clicks = new EventEmitter<string>();
也可以在
inputs
和outputs
数组中为属性指定别名:@Directive({
outputs: ['clicks: myClick']
}); -
模板表达式操作符
管道
安全导航操作符9.1 管道操作符(|)
适用于小型转换。
管道是一个简单的函数,它接受一个输入值,并返回转换结果。<div>
Title through uppercase pipe: {{ title | uppercase }}
</div>管道操作符可以串联:
<div>
Title through a pupe chain: {{ title | uppercase | lowercase }}
</div>还可以使用参数:
<div>
Birthday: {{ currentHero?.birthdate | date: 'longDate' }}
</div>json
管道用于调试绑定:<div>
{{ currentHero | json }}
</div>运行结果如图 9-1 所示:
9.2 安全导航操作符(?.)和空属性路径
?.
安全导航操作符用来保护出现在属性路径中的null
和undefined
值,保护视图渲染。The current hero's name is {{ currentHero?.name }}
如果
currentHero
不存在,那么上面的代码不加?.
会发生什么?
草,整个视图都不见了。
用于替代*ngIf
和&&
解决方案。<code>*ngIf 解决方案</code>
<p><code>fuck.name: </code><span *ngIf="fuck">{{ fuck.name }}</span></p>
<code>&& 解决方案</code>
<p><code>fuck.name: </code><span>{{ fuck && fuck.name }}</span></p>9.3 非空断言操作符(!)
值得一提的是非空断言操作符不会防止出现null或undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检查"。
如果我们打开了严格空值检查,那就要用到这个模板操作符,而其它情况下则是可选的。
在 TypeScript 2.0 中,我们可以使用
--strictNullChecks
标志强制开启 严格空值检查。TypeScript就会确保不存在意料之外的null或undefined。
在这种模式下,有类型的变量默认是不允许null或undefined值的,如果有- 未赋值的变量,
- 或者试图把null或undefined赋值给不允许为空的变量,
- 如果类型检查器在运行期间无法确定一个变量是null或undefined,
类型检查器就会抛出一个错误。
在用
*ngIf
来检查过hero
是已定义的之后,就可以断言hero
属性一定是已定义的。<div *ngIf="hero">
The hero's name is {{ hero!.name }}.
</div>