语法概览
image.png
image.png
image.png

3.1 数据绑定

image.png
image.png
在上述绑定类型中,除了插值,在’=’的左侧都会有一个目标名称,它可以被[]、()包裹,或者加上一个前缀,这被称为绑定目标。而’=’右侧或者插值符号{{}}中的部分被称为绑定源。
Property, DOM对象属性;Attribute,HTML标签特性。
image.png

  • 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数据。

  1. @Component({
  2. selector: "list-item",
  3. templateUrl: 'app/list/item.component.html',
  4. styleUrls: ['app/list/item.component.css']
  5. })
  6. export class ListItemComponent {
  7. @Input() contact: any = {};
  8. @Output() routerNavigate = new EventEmitter<number>();
  9. goDetail(num: number) {
  10. this.routerNavigate.emit(num);
  11. }
  12. }
  13. <a (click)="goDetail(contact.id)"></a>

父组件

  1. <ul>
  2. <li *ngFor="let contact of cpntacts">
  3. <list-li [contact]="contact" (routerNavigate)="routerNavigate($event)"></list-li>
  4. </li>
  5. </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指令内部才能正常运行。

  1. @NgModule({
  2. imports: [
  3. BrowserModule,
  4. FormsModule
  5. ],
  6. declarations: [
  7. AppComponent,
  8. FormComponent
  9. ],
  10. bootstrap: [AppComponent]
  11. })
  12. 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的集合中。

单选框

  1. <input type="radio" name="sex" [(ngModel)]="curContact.sex" value="female" />女
  2. <input type="radio" name="sex" [(ngModel)]="curContact.sex" value="male" />男

复选框

  1. <input type="checkbox" name="lock" [(ngModel)]="curContact.lock" />禁用

单选下拉框

  1. export class FormComponent {
  2. interests: any[] = [
  3. { value: 'reading', display: 'read' },
  4. { value: 'traveling', display: 'travel' },
  5. { value: 'sport', display: 'sport' },
  6. ]
  7. }
  8. <select name="interesValue" [(ngModel)]="curContact.interestValue">
  9. <option *ngFor="let interest of interests" [value]="interest.value">
  10. {{ interest.display }}
  11. </option>
  12. </select>

也可以返回对象类型

  1. <select name="interestObj" [(ngModel)]="curContact.interestObj">
  2. <option *ngFor="let interest of interests" [ngValue]="interest">
  3. {{ interest.display }}
  4. </option>
  5. </select>

多选下拉框

  1. <select multiple name="interestMul"[(ngModel)]="curContact.interestMul">
  2. <option *ngFor="let interest of interests" [value]="interest.value">
  3. {{ interest.display }}
  4. </option>
  5. </select>

返回的是数据的数组

模板局部变量

模板对DOM元素对指令的引用,可以使用在当前元素,兄弟元素或任何子元素中

DOM元素局部变量

  1. <li>
  2. <label for="name">name:</name>
  3. <input type="text" #contactName name="contactName" id="contactName" />
  4. <input type="number" ref-telNum name="telNum" id="telNum" />
  5. <p> {{ contactName.value }} -- {{ telNum.value }}</p>
  6. </li>

会把局部变量设置为对当前DOM的引用。可以在其他元素中直接使用该元素的DOM属性。

表单指令局部变量

NgForm 表单局部变量

NgModel 控件局部变量

表单状态

状态 true / false
valid 表单值是否有效
pristine 表单值是否未改变
dirty 表单值是否已改变
touched 表单是否已被访问过
untouched 表单是否未被访问过

NgModelGroup指令

对表单内容进行分组。

  1. <form #concatForm="ngForm">
  2. <fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup">
  3. <input type="text" name="firstname" [(ngModel)]="curContact.firstname" required />
  4. <input type="text" name="lastname" [(ngModel)]="curContact.lastname" required />
  5. </fieldset>
  6. <fieldset ngModelGroup="addressGroup" #addressGroup="ngModelGroup">
  7. <input type="text" name="street" [(ngModel)]="curContact.street" required />
  8. <input type="text" name="zip" [(ngModel)]="curContact.zip" required />
  9. </fieldset>
  10. </form>

此时concatForm.value的值为

  1. {
  2. nameGroup: {
  3. firstname: '',
  4. lastname: ''
  5. },
  6. addressGroup: {
  7. street: '',
  8. zip: '',
  9. city: ''
  10. }
  11. }

ngSubmit事件

submit按钮的操作,并负责控制表单的提交流程。

  1. <form #contactForm="ngForm" (ngSubmit)="doSubmit(contactForm.value)">
  2. <button type="submit" class="btn btn-default" [disabled]="!contactForm.valid">add</button>
  3. </form>
  4. export class FormComponent {
  5. doSubmit(formValue: any) {}
  6. }

自定义表单样式

状态 为true时CSS类 为false时CSS类
控件是否已被访问过 ng-touched ng-untouched
控件值是否已经变化 ng-dirty ng-pristine
控件值是否有效 ng-valid ng-invalid

3.3.3 表单校验

表单内置校验

  • required 判断表单控件值是否为空
  • minlength 判断表单控件值的最小长度
  • maxlength 判断表单控件值的最大长度
  • pattern 判断表单控件值得匹配规则

    表单自定义校验

    创建

    1. const EMAIL_REGEXP = new RegExp("[a-z0-9]+@[a-z0-9]+.com");
    2. const TEL = new RegExp("1[0-9]{10}");
    3. export function validateUserName(c: FormControl) {
    4. return (EMAIL_REGEXP.test(c.value) || TEL.test(c.value)) ? null : {
    5. userName: {
    6. valid: false,
    7. errorMsg: 'error'
    8. }
    9. }
    10. }

    使用

    这里只介绍模型驱动方式构建得表单如何使用自定义构建。使用模型驱动方式构建表单,要在表单组件所在模块代码中导入ReactiveFormsModule,并在模块@NgModule元数据imports数组中加入ReactiveFormsModule
    1. @NgModule({
    2. imports: [BrowserModule, ReactiveFormsModule]
    3. declarations: [AppComponent, FormComponent],
    4. bootstrap: [AppComponent]
    5. })
    6. export class AppModule { }
    1. @Component({
    2. selector: 'add-contact',
    3. template: `
    4. <form [formGroup]="customForm">
    5. <input type="text" formControlName="customName" />
    6. </form>
    7. `
    8. })
    9. export class FormComponent {
    10. customForm = new FormGroup({
    11. customName: new FormControl('', validateUserName)
    12. })
    13. }

    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属性用来指定管道的名称。

  1. @Pipe({ name: 'sexReform' })
  2. export class SexReform implements PipeTransform {
  3. transform(val: string): string {
  4. switch(val) {
  5. case 'male': return 'man';
  6. case 'female': return 'woman';
  7. default: return 'unknow';
  8. }
  9. }
  10. }

实现PipeTransform,实现transform()方法,第一个值是要被转换的值,后面有若干个可选转换参数,该方法要返回一个转换后的值。

3.4.4 管道的变化监测

纯管道

只有监测到输入值发生纯变更时才会调用纯管道的transform方法来实现数据转换,从而将数据更新到页面上。纯变更指基本数据类型输入值变更或对象引用的更改。

非纯管道

  1. @Pipe({
  2. name: 'selectContact',
  3. pure: false
  4. })

每个变化周期都会调用非纯管道