[Angular2] Template Syntax(模板语法)-(3)

NgModel双向数据绑定

NgModel属性用于双向数据绑定,既显示数据属性又接受用户的改变:

<input [(ngModel)]="currentHero.firstName">

也可以使用前缀方式:

<input bindon-ngModel="currentHero.firstName">

我们看一看关于双向数据绑定的发展历程。之前提到过,可以使用<input>元素的value属性和input事件实现跟NgModel相同的效果:

<input [value]="currentHero.firstName"
       (input)="currentHero.firstName=$event.target.value" >

这样子太繁琐了。我们需要记住元素要设置的属性和监听的事件。ngModel指令隐藏了这些细节。ngModel包装了元素的value属性,监听input事件,并且提供了自己的事件。

<input
  [ngModel]="currentHero.firstName"
  (ngModelChange)="currentHero.firstName=$event">

这个还有改进的地方。为什么我们要绑定两次呢?Angular应该能做到用一个声明来读取和设置组件书数据属性,那就是[()]

<input [(ngModel)]="currentHero.firstName">

在内部,Angular会将ngModel映射为ngModel输入属性和ngModelChange输出属性。

当然,在一些场景下还是有理由将[(ngModel)]拆成两个绑定的。例如,强制将用户的输入转换成大写:

<input
  [ngModel]="currentHero.firstName"
  (ngModelChange)="setUpperCaseFirstName($event)">

内建指令

下面是一些常见的内建指令。

NgClass

我们通常通过添加或移除CSS类来控制元素的外观。我们可以绑定NgClass来同时添加或移除多个CSS类。

之前提到过使用class绑定来添加或移除单个CSS类:

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

NgClass指令可以一次添加或移除多个CSS类。使用NgClass的方式是为它绑定一个key-value对象。对象的key是CSS类名;value为true时添加该类,为false时移除该类。

假设组件提供了setClasses方法管理CSS类的状态:

setClasses() {
  let classes =  {
    saveable: this.canSave,      // true
    modified: !this.isUnchanged, // false
    special: this.isSpecial,     // true
  }
  return classes;
}

NgClass属性绑定到setClasses方法:

<div [ngClass]="setClasses()">This div is saveable and special</div>

NgStyle

之前提到过使用style绑定来添加或移除单个样式:

<div [style.fontSize]="isSpecial ? 'x-large' : 'smaller'" >
  This div is x-large
</div>

NgStyle指令可以一次添加或移除多个内联样式。使用NgStyle的方式是为它绑定一个key-value对象。对象的key是样式名称;value就是样式的值。

假设组件提供了setStyles方法管理样式:

setStyles() {
  let styles = {
    // CSS property names
    'font-style':  this.canSave      ? 'italic' : 'normal',  // italic
    'font-weight': !this.isUnchanged ? 'bold'   : 'normal',  // normal
    'font-size':   this.isSpecial    ? '24px'   : '8px',     // 24px
  }
  return styles;
}

NgStyle属性绑定到setStyles方法:

<div [ngStyle]="setStyles()">
  This div is italic, normal weight, and extra large (24px)
</div>

NgIf

NgIf指令可以控制是否想DOM添加或移除一个元素。绑定的表达式值为真值时添加元素,为假值时移除元素。

<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>
<!-- not displayed because nullHero is falsey.
 `nullHero.firstName` never has a chance to fail -->
<div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>

<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>

我们可以也使用class绑定或style绑定控制显示或隐藏DOM元素:

<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>

<!-- HeroDetail is in the DOM but hidden -->
<hero-detail [class.hidden]="isSpecial"></hero-detail>

<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>

但是隐藏DOM元素跟使用NgIf移除是不一样的。当我们隐藏DOM元素时,元素仍然在DOM中。相应的组件都是存在的,保留着它们的状态,仍然消耗着内存,Angular可能会继续检查它的属性的变化。而NgIf值为false时,Angular会将元素中DOM中移除,释放掉组件,不再占用资源。

NgSwitch

我们使用NgSwitch指令来显示多个元素中的一个。

<span [ngSwitch]="toeChoice">
  <span *ngSwitchWhen="'Eenie'">Eenie</span>
  <span *ngSwitchWhen="'Meanie'">Meanie</span>
  <span *ngSwitchWhen="'Miney'">Miney</span>
  <span *ngSwitchWhen="'Moe'">Moe</span>
  <span *ngSwitchDefault>other</span>
</span>

注意,ngSwitch用的是属性绑定,不用加*,而ngSwitchWhenngSwitchDefault前面是加了*的。

NgFor

NgFor是一个repeater指令。

我们的目标是现实一个列表,我们用一个HTML片段定义单个列表项如何显示,然后告诉Angular用这个HTML片段作为模板来渲染列表中的每一项。

例如,对<div>使用NgFor

<div *ngFor="#hero of heroes">{{hero.fullName}}</div>

也可以对组件元素使用NgFor

<hero-detail *ngFor="#hero of heroes" [hero]="hero"></hero-detail>

赋值给*ngFor的字符串不是模板表达式,而是一个微语法(microsyntax)。例子中的#hero of heroes的意思是:对heroes数组中的每一项,将它赋值给本地变量herohero在每次迭代的HTML中可访问。Angular会将该绑定转换成一系列的元素和绑定。

NgFor索引

ngFor指令有个索引index,从0开始。我们可以用一个本地变量来捕获索引并在模板中使用。

<div *ngFor="#hero of heroes; #i=index">{{i + 1}} - {{hero.fullName}}</div>

还有一些值如lastevenodd

NgFor指令在列表很大时性能可能并不是很好。例如,我们从服务器更新heroes数组,新的数组中可能大部分都是之前显示的,我们可以根据id来判断每个hero是否改变。但是Angular只会看到新的数组引用,它会移除掉之前的所有元素,根据新数组重新构建并插入元素。

我们可以提供一个tracking函数来告诉Angular如何识别数组项是否一样:具有相同的hero.id的hero是相同的。

trackByHeroes(index: number, hero: Hero) { return hero.id; }

设置NgForTrackBy指令为定义的tracking函数,Angular提供了好几种绑定语法,例如:

<div *ngFor="#hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
<div *ngFor="#hero of heroes" *ngForTrackBy="trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>

*<template>

我们注意到在使用NgForNgIfNgSwitch时,用到了一个很奇怪的语法,在指令名前加前缀*

*是个语法糖,让指令的阅读和编写更加简单。NgForNgIfNgSwitch指令添加和移除的元素都包装在<template>中。但是我们并没有看到<template>,因为*前缀语法让我们可以忽略它,从而更多地关注我们要添加、移除或者重复的HTML元素。

展开*ngIf

我们可以将*前缀语法展开成模板语法。例如使用*ngIf时:

<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>

第一步,将ngIf(没有星号的)和它的内容转换成分配给template指令的表达式:

<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail>

第二步(也是最后一步),将HTML 放到<template>标签内,并添加ngIf属性绑定:

<template [ngIf]="currentHero">
  <hero-detail [hero]="currentHero"></hero-detail>
</template>

展开*ngSwitch

*ngSwitch的转换也是类似的:

<span [ngSwitch]="toeChoice">

  <!-- with *NgSwitch -->
  <span *ngSwitchWhen="'Eenie'">Eenie</span>
  <span *ngSwitchWhen="'Meanie'">Meanie</span>
  <span *ngSwitchWhen="'Miney'">Miney</span>
  <span *ngSwitchWhen="'Moe'">Moe</span>
  <span *ngSwitchDefault>other</span>

  <!-- with <template> -->
  <template [ngSwitchWhen]="'Eenie'"><span>Eenie</span></template>
  <template [ngSwitchWhen]="'Meanie'"><span>Meanie</span></template>
  <template [ngSwitchWhen]="'Miney'"><span>Miney</span></template>
  <template [ngSwitchWhen]="'Moe'"><span>Moe</span></template>
  <template ngSwitchDefault><span>other</span></template>

</span>

展开*ngFor

*ngFor转换也是类似的。转换之前:

<hero-detail *ngFor="#hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>

ngFor转换成template指令:

<hero-detail template="ngFor #hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>

进一步转换成<template>元素:

<template ngFor #hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes">
  <hero-detail [hero]="hero"></hero-detail>
</template>

Local template variable(本地模板变量)

本地模板变量用于在元素之间传递数据。例如使用<template>ngFor中:

<template ngFor #hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes">
  <hero-detail [hero]="hero"></hero-detail>
</template>

#前缀hero意味着我们定义了一个变量hero。也可以使用var-前缀。

引用模板变量

我们可以在同一个元素、兄弟元素、子元素中使用本地模板变量。

<!-- phone refers to the input element; pass its `value` to an event handler -->
<input #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>

<!-- fax refers to the input element; pass its `value` to an event handler -->
<input var-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>

NgForm和本地模板变量

看这个HTML表单:

<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
  <div class="form-group">
    <label for="name">Name</label>
    <input class="form-control" required ngControl="firstName"
      [(ngModel)]="currentHero.firstName">
  </div>
  <button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>

这个本地变量theForm的值到底是什么呢?

如果没有Angular,表单是一个HTMLFormElement。而在Angular中,它实际上是ngForm,Angular内建指令NgForm的引用,它包装了HTMLFormElement,并提供了一些额外的特性。

Input and output property(输入属性和输出属性)

之前我们关注的都是绑定声明右侧的模板表达式和模板声明,也就是数据绑定源。接下来再看一下绑定目标,绑定声明左侧的指令属性。这些指令属性必须声明为输入还是输出。

绑定目标就是绑定符号([]()[()])内的属性或事件,绑定源就是引号("")或这插值绑定({})内的内容。

只有显式标识为inputs或outputs的属性才能作为绑定目标。

例如HeroDetailComponent中:

<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>

HeroDetailComponent.heroHeroDetailComponent.deleteRequest都是绑定目标。HeroDetailComponent.hero在小括号内,它是个属性绑定的目标;HeroDetailComponent.deleteRequest在中括号内,它是个事件绑定的目标。

目标属性必须被显式地声明为输入或者输出:

@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();

或者在指令元数据的inputsoutputs数组中指定:

@Component({
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})

输入/输出属性的别名

有时候我们希望为输入/输出属性提供一个对外的名称。

例如,我们想绑定一个事件属性myClick

<div (myClick)="clickMessage=$event">click with myClick</div>

将组件的clicks事件属性起个别名myClick

@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...

或者实在元数据中指定:

@Directive({
  outputs:['clicks:myClick']  // propertyName:alias
})

Template expression operator(模板表达式运算符)

模板表达式扩展了一些JavaScript没有的操作符: pipe和Elvis。

pipe operator ( | )

有时候表达式的值在绑定前需要做一些转换。例如,将数字转换成货币格式、将字符串转成大写、对数组进行过滤或者排序。

管道(Pipe)个简单的函数,接收出入值,返回转换后的值。例如:

<!-- Force title to uppercase -->
<div>{{ title | uppercase }}</div>

管道运算符将左侧表达式的值传递给右侧的管道函数。

我们可以将多个管道函数链接起来:

<!-- Pipe chaining: force title to uppercase, then to lowercase -->
<div>{{ title | uppercase | lowercase }}</div>

也可以对管道函数进行配置:

<!-- pipe with configuration argument => "February 25, 1970" -->
<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>

json是个很有用的调试工具:

<div>{{currentHero | json}}</div>

<!-- Output:
 { "firstName": "Hercules", "lastName": "Son of Zeus",
 "birthdate": "1970-02-25T08:00:00.000Z",
 "url": "http://www.imdb.com/title/tt0065832/",
 "rate": 325, "id": 1 }
-->

Elvis operator ( ?. )

这个操作符是为了保证属性路径中有null或undefined值时不会出错:

The current hero's name is {{currentHero?.firstName}}

这样,即使currentHero为null,Angular也不会报错了。

参考资料

Angular官方文档

你可能感兴趣的:([Angular2] Template Syntax(模板语法)-(3))