3.1 数据绑定


在上述绑定类型中,除了插值,在’=’的左侧都会有一个目标名称,它可以被[]、()包裹,或者加上一个前缀,这被称为绑定目标。而’=’右侧或者插值符号{{}}中的部分被称为绑定源。
Property, DOM对象属性;Attribute,HTML标签特性。
- DOM对象与HTML标签特性并不是一一对应的,但有少量属性两个都是 id title class等
通常HTML标签代表初始值,初始值变化后就不再发生改变。DOM对象代表当前值,会随着属性值的变化而变化
3.1.1 插值
{{ }} 可以在标签内输出变量,也可以是一个合法的模板表达式,首先进行求值,然后转换成一个字符串输出带页面上。
<p>512 + 512 = {{ 512 + 512 }}</p>
表达式可以调用宿主组件的函数,<p>{{ detail.getName() }}</p>3.1.2 模板表达式
类似于JS的表达式,大部分js表达式都是合法的模板表达式。应用在插值语法的双大括号中和属性绑定’=’右侧引号中。Angular会执行这个表达式并将值分配给一个绑定目标的属性,这个目标可能是一个HTML元素,组件或者指令。但不能使用这些引发副作用的表达式
带new运算符的表达式
- 赋值表达式(=、+=、-=)等
- 带有;或,的链式表达式
- 自增或自减
其他JavaScript语法不同且值得注意的特性包括
- 不支持位运算符
- 部分模板表达式操作符被赋予了新的含义,如管道操作符(|)和安全导航操作符(?.)
模板表达式上下文
通常是它所在组件的实例,也可以包括组件之外的对象,如模板局部变量。在通讯录例子的联系人详情页面,模板表达式的上下文,就是它的组件实例。
需要特别注意的是,模板表达式不能引用任何全局命名空间中的成员。如window和document,也不能调用console.log()或者Math.random()等方法。
书写原则
- 避免视图变化的副作用
一个模板表达式只能改变目标属性的值,不应改变应用的任何状态,Angular的’单向数据流’模式正是基与这条原则而来。在单独的渲染过程中,视图应该是可预测到的,不必担心在读取组件值时会不小心改变其他的一些展示值。
- 能够高效的执行
Angular执行表达式的频率远超我们的想象,触发任何一次的键盘或者鼠标事件,这些表达式都可能会被执行。当计算的成本比较大时,可以考虑缓存那些从其他值计算得出的值
- 使用简单的语句
避免编写一些比较复杂的模板表达式
- 幂等性优先
表达式应应遵循幂等性优先原则。幂等的表达式总是会返回完全一致的东西,这样就没有副作用,并能提升Angular变化监测的性能,但表达式的返回值还是会随着它所依赖的值变化而变化。
3.1.3 属性绑定
单向的数据绑定,数据从组件类流向模板。属性绑定不能用来从目标元素获取值,或者调用目标元素的方法,也就是说目标元素的值只能被设置,不能被读取。但我们可以使用@ViewChild和@ContentChild来读取目标元素或调用它的方法。
DOM元素属性绑定
最常用的属性绑定是把元素的属性绑定到组件的属性上。div元素的title属性会被绑定到组件的titleText属性上<div [title]="titleText">hello world</div>
也可以使用bind-前缀的形式来实现<div bind-title="titleText">hello world</div>
下面这个例子设置Angular指令的属性<div [ngStyle]="styles">[ngStyle] 绑定styles属性</div>
自定义属性<user-detail [user]="currentUser"></user-detail>
HTML标签特性绑定
推荐使用DOM元素属性绑定,但当元素没有对应的属性可绑定的时候,则可以使用HTML标签特性绑定来设置值。例如table中colspan或rowspan<td [attr.colspan]="{{ 1 + 2 }}">合并单元格</td>
CSS类绑定
<div class="red font14" [class]="changeGreen"></div>
使用[class]changeGreen会重写这个元素全部的class。<div [class.footer]="show"></div>
show为true,footer添加到class中,否则就没有footer
Style绑定
<button [style.font-size.px]="large"></button><button [style.background-color]="large"></button>
3.1.4 事件绑定
事件绑定时一种单项数据绑定模式,数据从模板流向组件。Angular通过监听用户操作事件来执行对应绑定的方法。<a class="edit" (click)="editContact()">edit</a>
模板语句
“=”右侧的部分是模板语句,它是用来响应由绑定目标所触发的事件。模板语句可以帮助我们实现接收用户的输入来更新应用的状态。模板语句跟模板表达式一样,与JavaScript的表达式类似,但两者的解析器是不同的,模板语句除了支持”=”赋值操作外,也支持用分号或者逗号串联起多条语句。有一些表达式是不被支持的。
- 赋值操作, +=、-=
- 自增和自减操作
- new
- 位运算符
- 模板表达式运算符
模板语句和模板表达式一样,只能访问其上下文环境的成员,模板语句的上下文也可以包含组件之外的对象,如模板局部变量和事件绑定语句中的$event
目标事件
在小括号中事件名表示目标事件,<a class="edit" (click)="editContact">edit</a>除了(),也可以带on- 前缀来标记目标事件<a class="edit" on-click="editContact">edit</a>
也可以是自定义指令<a class="edit" (myClick)="editContact">edit</a>
会优先判断是否是已知指令的事件。如果事件名既不是某个已知指令的事件,也不是元素事件,Angular就会抛出一个”未知指令”的错误。
$event
在JS中,当用户单机某个元素,触发元素绑定的事件,执行模板语句。通过$event事件对象来获取事件的信息。目标事件的类型决定了事件对象的形态,目标事件可以是DOM元素事件,也可以是自定义事件。目标元素是原生DOM元素事件,则$event将一个包含target和target.value属性的DOM事件对象。
自定义事件
组件触发自定义事件可以借助EventEmitter。在组件中可以创建一个EventEmitter,并将输出属性的形式暴露出来。父组件通过绑定这个输出属性来自定义一个事件,在组件中定义调用EventEmitter.emit(payload)来触发自定义属性,其中payload是任何值,父组件绑定的事件可以通过$event来访问payload数据。
@Component({selector: "list-item",templateUrl: 'app/list/item.component.html',styleUrls: ['app/list/item.component.css']})export class ListItemComponent {@Input() contact: any = {};@Output() routerNavigate = new EventEmitter<number>();goDetail(num: number) {this.routerNavigate.emit(num);}}<a (click)="goDetail(contact.id)"></a>
父组件
<ul><li *ngFor="let contact of cpntacts"><list-li [contact]="contact" (routerNavigate)="routerNavigate($event)"></list-li></li></ul>
当routerNavigate事件触发时,调用父组件的routerNavigate()方法,在$event中传入对应联系人的id.
3.1.5 双向数据绑定
<input [value]="currentUser.firstName" (input)="currentUser.firstName=$event.target.value"></input>属性绑定实现了数据从组件到模板,事件绑定实现了数据从模板到组件。NgModel指令可以更方便地进行双向绑定。<input [(ngModel)]="currentUser.phoneNumber"></input>
展开<input [ngModel]="currentUser.phoneNumber" (ngModelChange)="addCodeForPhoneNumber($event)"></input>
3.1.6 输入属性和输出属性
在绑定声明”=”右侧部分,称为数据绑定的源。而”=”左侧的部分,称为数据的目标。
3.2 内置指令
ngClass ngStyle
3.3 表单
3.3.1 NgForm
负责处理表单内的页面逻辑,为普通表单元素扩充了很多额外的特性,所有的表单指令都要在NgForm指令内部才能正常运行。
@NgModule({imports: [BrowserModule,FormsModule],declarations: [AppComponent,FormComponent],bootstrap: [AppComponent]})export class AppModule { }
使用表单指令要引入FormModule.
NgForm指令控制了通过NgModel指令和name属性创建的控制类,也会跟踪控件类的属性变化 。
3.3.2 NgModel指令
表单数据绑定的核心,所有的特性依赖它来实现<input type="text" name="contactName" [ngModel]="curContact.name" /><input type="text" name="contactName" [(ngModel)]="curContact.name" />
使用NgModel属性绑定,必须给该控件添加name属性。因为NgForm指令会为表单建立一个控件对象FormControl的集合,以此来作为表单控件的容器。控件的NgModel属性绑定会以name作为唯一标识符来注册并生成一个FormControl,将其加入到FormControl的集合中。
单选框
<input type="radio" name="sex" [(ngModel)]="curContact.sex" value="female" />女<input type="radio" name="sex" [(ngModel)]="curContact.sex" value="male" />男
复选框
<input type="checkbox" name="lock" [(ngModel)]="curContact.lock" />禁用
单选下拉框
export class FormComponent {interests: any[] = [{ value: 'reading', display: 'read' },{ value: 'traveling', display: 'travel' },{ value: 'sport', display: 'sport' },]}<select name="interesValue" [(ngModel)]="curContact.interestValue"><option *ngFor="let interest of interests" [value]="interest.value">{{ interest.display }}</option></select>
也可以返回对象类型
<select name="interestObj" [(ngModel)]="curContact.interestObj"><option *ngFor="let interest of interests" [ngValue]="interest">{{ interest.display }}</option></select>
多选下拉框
<select multiple name="interestMul"[(ngModel)]="curContact.interestMul"><option *ngFor="let interest of interests" [value]="interest.value">{{ interest.display }}</option></select>
模板局部变量
模板对DOM元素对指令的引用,可以使用在当前元素,兄弟元素或任何子元素中
DOM元素局部变量
<li><label for="name">name:</name><input type="text" #contactName name="contactName" id="contactName" /><input type="number" ref-telNum name="telNum" id="telNum" /><p> {{ contactName.value }} -- {{ telNum.value }}</p></li>
会把局部变量设置为对当前DOM的引用。可以在其他元素中直接使用该元素的DOM属性。
表单指令局部变量
NgForm 表单局部变量
NgModel 控件局部变量
表单状态
| 状态 | true / false | |
|---|---|---|
| valid | 表单值是否有效 | |
| pristine | 表单值是否未改变 | |
| dirty | 表单值是否已改变 | |
| touched | 表单是否已被访问过 | |
| untouched | 表单是否未被访问过 |
NgModelGroup指令
对表单内容进行分组。
<form #concatForm="ngForm"><fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup"><input type="text" name="firstname" [(ngModel)]="curContact.firstname" required /><input type="text" name="lastname" [(ngModel)]="curContact.lastname" required /></fieldset><fieldset ngModelGroup="addressGroup" #addressGroup="ngModelGroup"><input type="text" name="street" [(ngModel)]="curContact.street" required /><input type="text" name="zip" [(ngModel)]="curContact.zip" required /></fieldset></form>
此时concatForm.value的值为
{nameGroup: {firstname: '',lastname: ''},addressGroup: {street: '',zip: '',city: ''}}
ngSubmit事件
submit按钮的操作,并负责控制表单的提交流程。
<form #contactForm="ngForm" (ngSubmit)="doSubmit(contactForm.value)"><button type="submit" class="btn btn-default" [disabled]="!contactForm.valid">add</button></form>export class FormComponent {doSubmit(formValue: any) {}}
自定义表单样式
| 状态 | 为true时CSS类 | 为false时CSS类 |
|---|---|---|
| 控件是否已被访问过 | ng-touched | ng-untouched |
| 控件值是否已经变化 | ng-dirty | ng-pristine |
| 控件值是否有效 | ng-valid | ng-invalid |
3.3.3 表单校验
表单内置校验
- required 判断表单控件值是否为空
- minlength 判断表单控件值的最小长度
- maxlength 判断表单控件值的最大长度
- pattern 判断表单控件值得匹配规则
表单自定义校验
创建
const EMAIL_REGEXP = new RegExp("[a-z0-9]+@[a-z0-9]+.com");const TEL = new RegExp("1[0-9]{10}");export function validateUserName(c: FormControl) {return (EMAIL_REGEXP.test(c.value) || TEL.test(c.value)) ? null : {userName: {valid: false,errorMsg: 'error'}}}
使用
这里只介绍模型驱动方式构建得表单如何使用自定义构建。使用模型驱动方式构建表单,要在表单组件所在模块代码中导入ReactiveFormsModule,并在模块@NgModule元数据imports数组中加入ReactiveFormsModule@NgModule({imports: [BrowserModule, ReactiveFormsModule]declarations: [AppComponent, FormComponent],bootstrap: [AppComponent]})export class AppModule { }
@Component({selector: 'add-contact',template: `<form [formGroup]="customForm"><input type="text" formControlName="customName" /></form>`})export class FormComponent {customForm = new FormGroup({customName: new FormControl('', validateUserName)})}
3.4 管道
3.4.1 介绍
可以按照开发者指定的规则将模板内的数据进行转换。使用|来连接左边的输入数据和右边的管道<p>my birthday is {{ birthday | date }}</p>管道参数
<p>my birthday is {{ birthday | date: 'MM/dd/y' }}</p>链式管道
可以连续使用不同的管道进行不同的处理{{ expression | pipeName1 | pipeName2 | ... }}3.4.2 内置管道
| 管道 | 类型 | 功能 | | —- | —- | —- | | DatePipe | 纯管道 | 日期管道,格式化日期 | | JsonPipe | 非纯管道 | 将输入数据对象经过JSON.stringify()方法转换后输出对象字符串 | | UpperCasePipe | 纯管道 | 将文本所有小写转成大写字母 | | LowerCasePipe | 纯管道 | 将文本所有大写转成小写字母 | | DecimalPipe | 纯管道 | 数值按特定的格式显示文本 | | CurrencyPipe | 纯管道 | 数值转成本地货币 | | PercentPipe | 纯管道 | 数值转成百分比 | | SlicePipe | 非纯管道 | 数组或字符串裁剪成新子集 |
3.4.3 自定义管道
必须引入Pipe和PipeTransform。Pipe装饰器告诉Angular这是一个管道类。name属性用来指定管道的名称。
@Pipe({ name: 'sexReform' })export class SexReform implements PipeTransform {transform(val: string): string {switch(val) {case 'male': return 'man';case 'female': return 'woman';default: return 'unknow';}}}
实现PipeTransform,实现transform()方法,第一个值是要被转换的值,后面有若干个可选转换参数,该方法要返回一个转换后的值。
3.4.4 管道的变化监测
纯管道
只有监测到输入值发生纯变更时才会调用纯管道的transform方法来实现数据转换,从而将数据更新到页面上。纯变更指基本数据类型输入值变更或对象引用的更改。
非纯管道
@Pipe({name: 'selectContact',pure: false})
每个变化周期都会调用非纯管道


