Angular4 - 模板语法

Angular4 - 模板语法

1. 模板语法的介绍

HTML 是 Angular 模板的语言。几乎所有的HTML语法都是有效的模板语法。 但值得注意的例外是<script>元素,它被禁用了,以阻止脚本注入攻击的风险。(实际上,<script>只是被忽略了。)
有些合法的 HTML 被用在模板中是没有意义的。<html>、<body>和<base>元素这个舞台上中并没有扮演有用的角色。剩下的所有元素基本上就都一样用了。这些元素存在于index.html中。
可以通过组件和指令来扩展模板中的 HTML 词汇。它们看上去就是新元素和属性。

那么模板语法是什么呢?
对于一个简单的html代码,我们可以在页面内配置css属性,绑定事件,设计element元素的位置达到我们想要的结果。以此类推到Angular模板中来说。模板语法规定了以下内容(自我总结):
*规定了html词汇(script被忽略,html,body,base没有意义,扩展指令和组件)
*扩展插值表达式和扩展模板表达式
*扩展了绑定语法(规定了属性绑定,事件绑定的语法以及双向绑定内容)
*模板引用变量 (请查看组件章节)
*Angular内置指令 (本章不会讲解,后续指令文章会有说明)
*Angular自定义指令 (本章不会讲解,后续指令文章会有说明)


总而言之,模板语法是规定了我们操作template内容以达到我们想要的结果

2. 插值表达式和模板表达式

(1) 插值表达式

{{}}称为插值表达式,可以将当前组件控制器中的公有变量直接取值然后显示其值。
I am {{employee.name}}
//angular.component.ts
export class AngularComponent implements OnInit {
  public employee: Employee = {
    name: 'rodchen',
    company: 'aug'
  }

  constructor() {
  }

  ngOnInit(): void {
  }
}
插值表达式还可以将计算过的字符串插入到HTML元素标签内或对标签的属性进行赋值
<h3>
  {{title}}
  <img src="{{heroImageUrl}}" style="height:30px">
</h3>

(2) 模板表达式

模板表达式产生一个值。 Angular执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令
。插值表达式其实就是模板表达式,只是上面我们只是将控制器中的变量值直接赋给元素。如果{{}}中发生了计算,那么我们可以称作模板表达式。模板表达式可以是数字计算
<p>The sum of 1 + 1 is {{1 + 1}}</p>
也可以是调用宿主组件的方法
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
模板表达式看起来javascript表达式,但是是有一些规定:

JavaScript中那些具有或可能引发副作用的表达式是被禁止的,包括:
赋值 (=, +=, -=, ...)
new运算符
使用;或,的链式表达式
自增或自减操作符 (++和--)


和 JavaScript语法的其它显著不同包括:
不支持位运算|和&
具有新的模板表达式运算符,比如|、?.和!。


(3) 模板表达式操作符

a) 管道操作符 ( | )

在绑定之前,表达式的结果可能需要一些转换。例如,可能希望把数字显示成金额、强制文本变成大写,或者过滤列表以及进行排序。
I am {{employee.name | uppercase}} //I am RODCHEN
具体用法可以参照 pipe,后面也会有专门的文章去学习。

b) 安全导航操作符 ( ?. ) 和空属性路径

Angular 的安全导航操作符 (?.) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。例如a)中的例子,如果employee是null,页面会发生是什么呢?
"I am"依然会渲染,但是会报错。
TypeError: Cannot read property 'name' of null in [null]. 
这时我们就可以使用安全导航操作符来规避这个错误。
I am {{employee?.name | uppercase}}

c) 非空断言操作符(!)

如果类型检查器在运行期间无法确定一个变量是null或undefined,那么它也会抛出一个错误。 我们自己可能知道它不会为空,但类型检查器不知道。 所以我们要告诉类型检查器,它不会为空,这时就要用到非空断言操作符。
Angular 模板中的**非空断言操作符(!)也是同样的用途。

例如,在用*ngIf来检查过hero是已定义的之后,就可以断言hero属性一定是已定义的。
src/app/app.component.html
 content_copy
<!--No hero, no text -->
<div *ngIf="hero">
  The hero's name is {{hero!.name}}
</div>
在 Angular 编译器把你的模板转换成 TypeScript 代码时,这个操作符会防止 TypeScript 报告 "hero.name可能为null或undefined"的错误。
与安全导航操作符不同的是,非空断言操作符不会防止出现null或undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检测"。
如果我们打开了严格控制检测,那就要用到这个模板操作符,而其它情况下则是可选的。

(4) 插值表达式的实现

<h3>
  {{title}}
  <img src="{{heroImageUrl}}" style="height:30px">
</h3>
表面上看,我们在元素标签之间插入了结果和对标签的属性进行了赋值。 这样思考起来很方便,并且这个误解很少给我们带来麻烦。 但严格来讲,这是不对的。插值表达式是一个特殊的语法,Angular 把它转换成了属性绑定。属性绑定后面会说到。

3. 绑定语法

数据绑定是一种机制,用来协调用户所见和应用数据。 虽然我们能往 HTML 推送值或者从 HTML 拉取值, 但如果把这些琐事交给数据绑定框架处理, 应用会更容易编写、阅读和维护。 只要简单地在绑定源和目标 HTML 元素之间声明绑定,框架就会完成这项工作。

(1) 绑定类型

a)单向从数据源到视图目标
插值表达式 {{expression}}
DOM Property绑定 [target]="expression"
HTML Attribute绑定 [attr.target]="expression"
CSS 类绑定 [class.class-name]="expression"
样式绑定 [target]="expression"
b) 单向从视图目标到数据源
事件绑定 (target)="statement"
c) 双向绑定
[(target)]="expression"

(2) HTML attribute 与 DOM property 的对比

介绍绑定之前,还是先看一个重要的问题:HTML attribute 与 DOM property 的对比.要想理解 Angular 绑定如何工作,重点是搞清 HTML attribute 和 DOM property 之间的区别。
attribute 是由 HTML 定义的。property 是由 DOM (Document Object Model) 定义的。
少量 HTML attribute 和 property 之间有着 1:1 的映射,如id。
有些 HTML attribute 没有对应的 property,如colspan。
有些 DOM property 没有对应的 attribute,如textContent。
大量 HTML attribute看起来映射到了property…… 但却不像我们想的那样!


attribute 初始化 DOM property,然后它们的任务就完成了。property 的值可以改变;attribute 的值不能改变。

举个例子: 
<input type="text" value="name" (input)="onPut($event)">
import {Component, OnInit} from '@angular/core';

@Component({
  selector: 'app-angular',
  templateUrl: './angular.component.html',
  styleUrls: ['./angular.component.css']
})

export class AngularComponent implements OnInit {
  constructor() {
  }

  ngOnInit(): void {
  }

  onPut(event) {
    console.log('DOM 属性: ' + event.target.value);
    console.log('HTML 属性: ' + event.target.getAttribute('value'));
  }
}
如下图所示,我们可以看到随着我们的输入,DOM属性会变化,但是html属性不会发生变化。



在看一个关于disabled的例子:
<button disabled>点击</button>
当前这个button是不可以点击的,因为出现了disabled属性。
<button disabled="false">点击</button>
当我将disables(html属性)设置为false,这个button还是不可以的。添加或删除disabled attribute会禁用或启用这个按钮。但 attribute 的值无关紧要,这就是我们为什么没法通过 <button disabled="false">仍被禁用</button>这种写法来启用按钮。
但是我们可以通过DOM属性来启用button。
<button [disabled]="false">点击</button>
模板绑定是通过 property 和事件来工作的,而不是 attribute。在 Angular 的世界中,attribute 唯一的作用是用来初始化元素和指令的状态。 当进行数据绑定时,只是在与元素和指令的 property 和事件打交道,而 attribute 就完全靠边站了。

4 绑定目标

(1) 单向从数据源到视图目标

a) 插值表达式

{{employee.name}}

b) 属性绑定(DOM属性绑定) 

//[target]="expression"

<input type="text" [value]="name" />
有些人喜欢用bind-前缀的可选形式,并称之为规范形式:
<input type="text" bind-value="name" />
属性绑定还是插值表达式?
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
当要渲染的数据类型是字符串时,没有技术上的理由证明哪种形式更好。 我们倾向于可读性,所以倾向于插值表达式。 建议建立代码风格规则,选择一种形式, 这样,既遵循了规则,又能让手头的任务做起来更自然。但数据类型不是字符串时,就必须使用属性绑定了。

c) attribute绑定

[attr.target]="expression"
可以通过attribute 绑定来直接设置 attribute 的值。

为什么 Angular 还提供了 attribute 绑定呢?
因为当元素没有属性可绑的时候,就必须使用 attribute 绑定。
<tr><td colspan="{{ 1 + 1 }}">One-Two</td></tr>


正如提示中所说,<td>元素没有colspan属性。 但是插值表达式和属性绑定只能设置属性,不能设置 attribute。我们需要 attribute 绑定来创建和绑定到这样的 attribute。

attribute 绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由attr前缀,一个点 (.) 和 attribute 的名字组成。 可以通过值为字符串的表达式来设置 attribute 的值。

这里把[attr.colspan]绑定到一个计算值:
 <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

 d) CSS 类绑定

借助 CSS 类绑定,可以从元素的class attribute 上添加和移除 CSS 类名。CSS 类绑定绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由class前缀,一个点 (.)和 CSS 类的名字组成, 其中后两部分是可选的。形如:[class.class-name]。

举个例子:
.color-class {
  color: red;
}
<div>
  <div class="color-class">
    <p>Test the css class bind</p>
  </div>
</div>
此时页面上的文字为红颜色。

然后我们使用[class]来绑定,可以把它改写为绑定到所需 CSS 类名的绑定;这是一个或者全有或者全无的替换型绑定。
<div>
  <div class="color-class" [class]="className">
    <p>Test the css class bind</p>
  </div>
</div>
这样的话就设置[class]上的类名到div上,class="color-class"会被替换掉。这种方式显得太暴力了点,可能我只想操作某一个class。那么可以使用下面的方式。可以绑定到特定的类名。 当模板表达式的求值结果是真值时,Angular 会添加这个类,反之则移除它。
<div [class.special]="isSpecial">The class binding is special</div>
还有一个ngClass内置指令,等到指令篇章学习。

e) 样式绑定

通过样式绑定,可以设置内联样式。
[style.style-property]
样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由style前缀,一个点 (.)和 CSS 样式的属性名组成。 
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
有些样式绑定中的样式带有单位。在这里,以根据条件用 “em” 和 “%” 来设置字体大小的单位。
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
还有一个ngStyle内置指令,等到指令篇章学习。

(2) 单向从视图目标到数据源

a)事件绑定

(target)="statement"
圆括号中的名称 —— 比如(click) —— 标记出目标事件。在下面例子中,目标是按钮的 click 事件。
<button (click)="onSave()">Save</button>
有些人更喜欢带on-前缀的备选形式,称之为规范形式:
<button on-click="onSave()">On Save</button>

b) $event 和事件处理语句

绑定会通过名叫$event的事件对象传递关于此事件的信息(包括数据值)。

事件对象的形态取决于目标事件。如果目标事件是原生 DOM 元素事件, $event就是 DOM事件对象。
 <button (click)="onSave($event)">Save</button>

c) EventEmitter自定义事件

令使用 Angular EventEmitter 来触发自定义事件。 指令创建一个EventEmitter实例,并且把它作为属性暴露出来。 指令调用EventEmitter.emit(payload)来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过$event对象来访问。详情可以查看Output.

(3) 双向数据绑定

我们经常需要显示数据属性,并在用户作出更改时更新该属性。
在元素层面上,既要设置元素属性,又要监听元素事件变化。
Angular 为此提供一种特殊的双向数据绑定语法:[(x)]。 [(x)]语法结合了属性绑定的方括号[x]和事件绑定的圆括号(x)。

a) [( )] = 盒子里的香蕉

举例: 
//step.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>
//step.component.ts
import {Component, EventEmitter, HostListener, Input, Output} from '@angular/core';

@Component({
  selector: 'app-step',
  templateUrl: './step.component.html',
  styleUrls: ['./step.component.css']
})
export class StepComponent {
  @Input()
  size: number;

  @Output()
  sizeChange = new EventEmitter<number>();

  dec() { this.resize(- 1); }
  inc() { this.resize(+ 1); }

  resize(delta: number) {
    this.size = Math.min(40, Math.max(8, + this.size + delta));
    this.sizeChange.emit(this.size);
  }
}
//angular.component.html
<app-step [(size)]="fontSizePx"></app-step>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
//angular.compoent.ts
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {Employee} from './angular.module';

@Component({
  selector: 'app-angular',
  templateUrl: './angular.component.html',
  styleUrls: ['./angular.component.css'],
})

export class AngularComponent {
  public fontSizePx: number = 12;


  constructor() {
  }
}
StepComponent: size的初始值是一个输入值,来自属性绑定。(译注:注意size前面的@Input) 点击按钮,在最小/最大值范围限制内增加或者减少size。 然后用调整后的size触发sizeChange事件。

双向绑定语法实际上是属性绑定和事件绑定的语法糖。 Angular将StepComponent的绑定分解成这样:
<app-step [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-step>

b) ngModel

使用 ngModel 时需要 FormsModule
在使用ngModel指令进行双向数据绑定之前,我们必须导入FormsModule并把它添加到Angular模块的imports列表中。
<input [(ngModel)]="currentHero.name">
回头看看name绑定,注意,你可以通过分别绑定到<input>元素的value属性和input事件来达到同样的效果。
<input [value]="currentHero.name" (input)="currentHero.name=$event.target.value" >
那样显得很笨重,谁会记得该设置哪个元素属性以及当用户修改时触发哪个事件? 你该如何提取输入框中的文本并且更新数据属性?谁会希望每次都去查资料来确定这些?

ngModel指令通过自己的输入属性ngModel和输出属性ngModelChange隐藏了那些细节。
<input [ngModel]="currentHero.name" (ngModelChange)="currentHero.name=$event">
ngModel输入属性会设置该元素的值,并通过ngModelChange的输出属性来监听元素值的变化。

*各种元素都有很多特有的处理细节,因此NgModel指令只支持实现了ControlValueAccessor的元素, 它们能让元素适配本协议。 <input>输入框正是其中之一。 Angular为所有的基础HTML表单都提供了值访问器(Value accessor),表单一章展示了如何绑定它们。

参考: https://www.angular.cn/guide/template-syntax

猜你喜欢

转载自blog.csdn.net/it_rod/article/details/79429457