Angular复习笔记4-模板
简介
模板是一种自定义的标准化页面,通过模板和模板中的数据结合,可以生成各种各样的网页。在Angular中,模板的默认语言是HTML,几乎所有的HTML语法在模板中都是适用的,但<script>标签是被禁止的,主要是为了防止JavaScript脚本注入攻击(即XSS)。同时一些HTML元素在模板中并不起什么作用,比如<html>、<body>、<base>等。除此之外,在Angular中可以通过组件和指令对模板的HTML元素进行扩展,这些扩展将以新的元素或属性的形式出现。
语法概览
续表:
数据绑定
数据绑定为应用程序提供了一种简单、一致的机制来管理与协调数据的显示,以及数据值的变化。这种机制可以从HTML里面取值和赋值,使得数据的读写变得更加简单、快捷。Angular提供了多种数据绑定方式,可以根据数据流动的方向分为三种,详见下表:
下图可以更直观地看出数据绑定的数据流向(箭头表示数据的流向)。
在上述绑定类型中,除插值外,在“=”的左侧都会有一个目标名称,它可以被[]、()包裹,或者加上一个前缀(bind-、on-、bindon-),这被称为绑定目标。而“=”右侧或者插值符号“{{}}”中的部分则被称为绑定源。
在数据绑定中有一个非常重要的概念需要区分一下:一个是Property,它是DOM属性,还有一个是Attribute,它是HTML元素特性。这两个词的区别在于:
此外,这两个的区别还体现在下面几点:
1、在大多数情况下,DOM对象属性与HTML标签特性并不是一一对应的,但有少量属性既是DOM对象属性又是HTML标签特性,如id、title、class(CSS类)等
2、通常HTML标签特性代表着初始值,初始化后就不再发生改变;而DOM对象属性代表着当前值,默认为初始值,但它会随着属性值的变化而变化。数据绑定是借助于元素与指令的DOM对象属性和事件来运作的,而不是HTML标签特性。
3、在Angular中,HTML标签特性的唯一作用就是用来进行元素和指令状态的初始化。
插值
数据绑定最常见的形式就是插值(Interpolation),默认使用的是双大括号“{{}}”的语法。使用插值可以在HTML元素标签和属性值内将变量输出。
<div>
{{item}}
</div>
<div>
{{5+3}}
</div>
<div>
{{item.getName()}}
</div>
双大括号中间通常是组件属性的变量名,还可以使用模板表达式,angular首先对他求值,然后转换成一个字符串显示在模板上。甚至可以调用宿主组件的函数。
模板表模板表达式
类似于JavaScript语言的表达式,绝大部分的JavaScript表达式都是合法的模板表达式。模板表达式应用在插值语法的双大括号中和属性绑定“=”右侧的引号中。Angular会执行这个表达式并将值分配给一个绑定目标的属性,这个绑定目标可能是一个HTML元素、组件或者指令。但不可以使用以下可能引发副作用的JavaScript表达式:
- 带有new运算符的表达式。
- 赋值表达式(=、+=、-=等)。
- 带有“;”或者“,”的链式表达式。
- 带有自增和自减操作(++和--)的表达式。
其他与JavaScript语法不同且值得注意的特性包括:
- 不支持位运算符(|和&)。
- 部分模板表达式操作符被赋予了新的含义,如管道操作符(|)和安全导航操作符(?.)等。
模板表达式的上下文
模板表达式的上下文通常就是宿主组件的实例,也可以是模板局部变量。
模板表达式不能引用全局变量如windows和document。也不能调用console和math等。
模板表达式的书写原则
- 避免视图变化的副作用。一个模板表达式只能改变目标属性的值,不应改变应用的任何状态,Angular的“单向数据流”模式正是基于这条原则而来的。在单独的渲染过程中,视图应是可预测到的,不必担心在读取组件值时会不小心改变其他的一些展示值。
- 能够高效地执行。Angular执行表达式的频率远超我们的想象,触发任何一次的键盘或者鼠标事件,这些表达式都可能会被执行。当计算的成本比较大时,可以考虑缓存那些从其他值计算得出的值。
- 使用简单的语句。避免编写一些比较复杂的模板表达式。
- 幂等性优先。表达式应遵循幂等性优先原则。幂等的表达式总是会返回完全一致的东西,这样就没有副作用了,并能提升Angular变化监测的性能,但表达式的返回值还是会随着它所 依赖的值变化而变化的。
属性绑定
属性绑定的数据流向就是从组件类流向模板,模板上元素的值只能被设置,不能被读取。但们可以使用@ViewChild和@ViewChildren来对模板元素的值或方法进行调用。
DOM元素属性绑定
最常用的属性绑定是把元素的属性绑定到组件的属性上。在下面的例子中,div元素的title属性会被绑定到组件的titleText属性上。
<div [title]="titleText"></div>
下面的例子是用来设置angular指令的属性的:
<div [ngStyle]="myStyle"></div>
此外,还可以使用属性绑定设置自定义组件的输入属性(这是父子组件间通信的重要途径)。示例代码如下:
<my-app [user]="currentUser"></my-app>
中括号
在属性绑定中,“=”左侧中括号的作用是让Angular执行“=”右侧的模板表达式,并将结果赋值给该目标属性。如果没有中括号,Angular就会把“=”右侧的模板表达式当作一个字符串常量,而不会计算该字符串。所以,如果赋值给目标属性的值是一个字符串字面量,那么推荐省略中括号。
在标准的HTML中常用这种方式来初始化HTML标签特性(Attribute)的值,在Angular中也可以用这种方式来初始化指令和组件属性的值。下面例子把detail属性初始化为一个固定的字符串,而不是模板表达式。detail属性的值在初始化之后将不再改变,而通过属性绑定的user将会随着模板表达式的值变化而变化。示例代码如下:
<user-detail detail="hello world" [user]="currentUser"></user-detail>
HTML标签特性绑定
Angular推荐使用DOM元素属性绑定,但当元素没有对应的属性可绑定的时候,则可以使用HTML标签特性绑定来设置值。例如<table>中的colspan或rowspan等HTML标签特性,是纯粹的HTML标签特性,并没有相对应的DOM元素属性可供绑定,如果直接用模板表达式赋值,如下例所示:
<table border=1>
<tr>
<td colspan="{{1+2}}">合并单元格</td>
</tr>
</table>
这将会出现一个模板解析的错误,因为colspan在<td>元素中并不是DOM元素属性,而是HTML标签特性。插值和属性绑定只能设置DOM元素属性,不能设置HTML标签特性。
HTML标签特性绑定在语法上类似于属性绑定,但中括号中的部分不是一个元素的属性名,而是由attr.前缀和HTML标签特性名称组成的形式,然后通过一个模板表达式来设置HTML标签特性的值的。上面的例子可以进行如下改造:
<table border=1>
<tr>
<td [attr.colspan]="{{1+2}}">合并单元格</td>
</tr>
</table>
CSS类绑定
正如上文所提到的,CSS类既属于HTML标签特性,又属于DOM对象属性,所以可以使用以上两种方式来完成属性绑定。示例代码如下:
<!--dom属性绑定-->
<div class="font4">font4号字体</div>
另外,Angular也给CSS类属性绑定提供了特有的绑定方式,即使用类似于[class.class-name]的语法形式来完成属性绑定——当被赋值为true时,
将class-name这个类添加到该绑定的标签上,否则将移除这个类。示例代码如下:
<div [class.blue]="isBlue">
如果isBlue返回true,则应用blue这个类
</div>
style样式绑定
HTML标签内联样式可以通过Style样式绑定的方式来设置。样式绑定在语法上采用形如[style.style-property]的写法。如设置按钮的背景色为蓝色,示例代码如下:
<button [style.background-color]="canClick?'blue':'grey'">
如果canClick的值为true,则显示为蓝色
</button>
在设置内联样式时,也可以带上单位:
<button [style.font-size.px]="isLarge?'18':'10'"></button>
<button [style.width.%]="isLarge?'100':'50'"></button>
样式属性可以采用烤肉串的font-size写法,也可以采用驼峰写法fontSize。
属性绑定和插值的关系
插值表达式{{}}会在编译时被编译成属性绑定的形式,它只是属性绑定的语法糖,向下面这种的:
<span>hello world <i>{{userName}}</i></span>
<span>hello world <i [innerHtml]="userName"></i></span>
事件绑定
事件绑定也是一种单向的事件绑定,数据从模板流向组件。如下面:
<button (click)="edit($event)">编辑</button>
事件绑定的语法是由“=”左侧小括号内的目标事件和“=”右侧引号中的模板语句组成的。=号左侧是事件名,右侧是引发事件后需要执行的操作,是一个模板语句,
模板语句和模板表达式不同的地方是模板语句可以是由多个模板表达式组合而成的,由“;”号分隔。
目标事件
小括号()里面表示的内容就是目标事件。目标事件可以是常见的元素事件(如click),也可以是自定义指令的事件如:
<a (myClick)="editIt()">编辑</a>
Angular在解析目标事件时,会优先判断是否匹配已知指令的事件,如果事件名既不是某个已知指令的事件,也不是元素事件,Angular就会抛出一个“未知指令”的错误。
$event事件对象
$event是事件对象的携带者,它是固定的写法。目标事件可以是DOM元素的事件,也可以是自定义事件。如果目标事件是原生的DOM元素事件,则$event是一个包含target和target.value属性的DOM事件对象。
<input type="text" [value]='currentUser.firstName' (input)="currentUser.firstName=$event.target.value">
在上面例子中,当用户更改输入框中的文本时,input事件会被触发,对应的模板语句就会被执行。此时,该模板语句的上下文中包含一个$event对象,通过$event.target.value可以将通过输入框输入的值绑定并修改firstName属性的值。
自定义事件
在Angular中,组件要触发自定义事件可以借助于EventEmitter。在组件中可以创建一个EventEmitter实例对象,并将其以输出属性的形式暴露出来。父组件通过绑定这个输出属性来自定义一个事件,在组件中调用EventEmitter.emit(payload)来触发这个自定义事件,其中payload可以传入任何值,父组件绑定的事件可以通过$event对象来访问payload的数据。在组件的章节中已经对这种自定义事件有了讲解,这里不再赘述。
双向绑定
上面提到的属性绑定和事件绑定都是单向数据绑定,而在实际开发中,有时也会碰到需要双向数据绑定的场景,例如在表单展示的功能中,一般需要将组件的数据显示到表单上,也需要将用户修改的数据更新到组件中。想实现这种双向绑定的效果,可以利用属性绑定和事件绑定结合的形式来处理。示例代码如下:
<input type="text" [value]="user.firstName" (input)="user.firstName=$event.target.value">
上面的代码属性绑定实现了从组件类到模板的数据流动,而input事件的绑定实现了从模板到组件类的数据流动。angular实现了NgMoel指令来更方便的实现双向绑定,如下:
上面使用ngModel输入属性和ngModelChange输出属性隐藏了一些繁琐的细节,提供了一个统一的编程接口来应对不同的DOM属性值的双向绑定。但使用起来还是非常不方便。angular可以使用一个更简单的语法来干这件事,那就是“[()]”语法。
<input type="text" [(ngModel)]="user.phoneNumber">
可以看到清爽很多,而上面那种写法是下面这种的展开形式,展开形式的写法是可以提供更灵活的逻辑,比如要将电话号码格式化后在显示回去:
<input type="text" [ngModel]="phoneNumber" (ngModelChange)="formatPhoneNumber($event)">
// 组件类:
//其他代码......
phoneNumber: string;
formatPhoneNumber(number: string) {
if (number.length === 11) {
console.log(number);
this.phoneNumber = `电话号码:${number}`;
} else {
this.phoneNumber = number;
}
}
输入和输出属性
输入和输出属性对应于装饰器@Input和@Outpu
@Input修饰的属性就是输入属性,输入属性用于属性绑定,数据流从等号([property]="item")左边流入等号右边
@Output修饰的属性就是输出属性,输出属性用于事件绑定,数据流从等号((event)="onEvent($event)")的右边流入等号的左边
输入和输出属性别名
@Input装饰器可以传入一个字符串来表示别名,在别的组件中就可以使用这个别名来进行属性绑定了。
@Input('name') myFullName: string;
此外,也可以通过在组件的元数据里面定义,语法形如:outputs:['组件属性名:别名']:
@Component({
outputs:['name:firstName']
})