<td [attr.colspan]="progressCode == 'WAITING' ? 11: 10 ">
attribute、class 和 style 绑定
模板语法为那些不太适合使用属性绑定的场景提供了专门的单向数据绑定形式。
attribute 绑定
可以通过attribute 绑定来直接设置 attribute 的值。
这是“绑定到目标属性 (property)”这条规则中唯一的例外。这是唯一的能创建和设置 attribute 的绑定形式。
本章中,通篇都在说通过属性绑定来设置元素的属性总是好于用字符串设置 attribute。为什么 Angular 还提供了 attribute 绑定呢?
因为当元素没有属性可绑的时候,就必须使用 attribute 绑定。
考虑ARIA,SVG和 table 中的 colspan/rowspan 等 attribute。 它们是纯粹的 attribute,没有对应的属性可供绑定。
如果想写出类似下面这样的东西,现状会令我们痛苦:
content_copy<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
</td></tr>
会得到这个错误:
content_copyTemplate parse errors: Can't bind to 'colspan' since it isn't a known native property (模板解析错误:不能绑定到 'colspan',因为它不是已知的原生属性)
正如提示中所说,<td>
元素没有colspan
属性。 但是插值表达式和属性绑定只能设置属性,不能设置 attribute。
我们需要 attribute 绑定来创建和绑定到这样的 attribute。
attribute 绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由attr
前缀,一个点 (.
) 和 attribute 的名字组成。 可以通过值为字符串的表达式来设置 attribute 的值。
这里把[attr.colspan]
绑定到一个计算值:
content_copy<table border1> <!-- expression calculates colspan=2 --> <tr><td [attr.colspan]"1 + 1">One-Two</td></tr> <!-- ERROR: There is no `colspan` property to set! <tr><td colspan="{{1 + 1}}">Three-Four</td></tr> --> <tr><td>Five</td><td>Six</td></tr> </table>
这里是表格渲染出来的样子:
One-Two | |
Five | Six |
attribute 绑定的主要用例之一是设置 ARIA attribute(译注:ARIA指可访问性,用于给残障人士访问互联网提供便利), 就像这个例子中一样:
src/app/app.component.htmlcontent_copy<!-- create and set an aria attribute for assistive technology --> <button [aria-label]"actionName">{{actionName}} with Aria</button>
CSS 类绑定
借助CSS 类绑定,可以从元素的class
attribute 上添加和移除 CSS 类名。
CSS 类绑定绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由class
前缀,一个点 (.
)和 CSS 类的名字组成, 其中后两部分是可选的。形如:[class.class-name]
。
下列例子示范了如何通过 CSS 类绑定来添加和移除应用的 "special" 类。不用绑定直接设置 attribute 时是这样的:
src/app/app.component.htmlcontent_copy<!-- standard class attribute setting --> <div class"bad curly special">Bad curly special</div>
可以把它改写为绑定到所需 CSS 类名的绑定;这是一个或者全有或者全无的替换型绑定。 (译注:即当 badCurly 有值时 class 这个 attribute 设置的内容会被完全覆盖)
src/app/app.component.htmlcontent_copy<!-- reset/override all class names with a binding --> "bad curly special" [class]"badCurly">Bad curly最后,可以绑定到特定的类名。 当模板表达式的求值结果是真值时,Angular 会添加这个类,反之则移除它。src/app/app.component.htmlcontent_copy<!-- toggle the "special" class on/off with a property --> <div [class.special]"isSpecial">The class binding is special</div> <!-- binding to `class.special` trumps the class attribute --> "special" ["!isSpecial">This one is not so special</div>虽然这是切换单一类名的好办法,但我们通常更喜欢使用NgClass指令来同时管理多个类名。
样式绑定通过样式绑定,可以设置内联样式。
样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由
src/app/app.component.htmlstyle
前缀,一个点 (.
)和 CSS 样式的属性名组成。 形如:[style.style-property]
。content_copystyle.color]"isSpecial ? 'red': 'green'">Red</button> background-color]"canSave ? 'cyan': 'grey'" >Save</button>有些样式绑定中的样式带有单位。在这里,以根据条件用 “em” 和 “%” 来设置字体大小的单位。
src/app/app.component.htmlcontent_copyfont-size.em]"isSpecial ? 3 : 1" >Bigfont-size.%]"!isSpecial ? 150 : 50" >Small</button>虽然这是设置单一样式的好办法,但我们通常更喜欢使用NgStyle指令来同时设置多个内联样式。
事件绑定 ((事件名))
前面遇到的绑定的数据流都是单向的:从组件到元素。
但用户不会只盯着屏幕看。他们会在输入框中输入文本。他们会从列表中选取条目。 他们会点击按钮。这类用户动作可能导致反向的数据流:从元素到组件。
知道用户动作的唯一方式是监听某些事件,如按键、鼠标移动、点击和触摸屏幕。 可以通过 Angular 事件绑定来声明对哪些用户动作感兴趣。
事件绑定语法由等号左侧带圆括号的目标事件和右侧引号中的模板语句组成。 下面事件绑定监听按钮的点击事件。每当点击发生时,都会调用组件的
src/app/app.component.htmlonSave()
方法。content_copy<button (click)"onSave()"</button>目标事件
圆括号中的名称—— 比如
src/app/app.component.html(click)
—— 标记出目标事件。在下面例子中,目标是按钮的 click 事件。content_copy有些人更喜欢带on-
前缀的备选形式,称之为规范形式:src/app/app.component.htmlcontent_copy<button on-click>On Save元素事件可能是更常见的目标,但 Angular 会先看这个名字是否能匹配上已知指令的事件属性,就像下面这个例子:src/app/app.component.htmlcontent_copy<!-- `myClick` is an event on the custom `ClickDirective` --> <div (myClick)"clickMessage=$event" clickable>click with myClick更多关于该
myClick
指令的解释,见给输入/输出属性起别名。
如果这个名字没能匹配到元素事件或已知指令的输出属性,Angular 就会报“未知指令”错误。
$event和事件处理语句
在事件绑定中,Angular 会为目标事件设置事件处理器。
当事件发生时,这个处理器会执行模板语句。 典型的模板语句通常涉及到响应事件执行动作的接收器,例如从 HTML 控件中取得值,并存入模型。
绑定会通过名叫$event
的事件对象传递关于此事件的信息(包括数据值)。
事件对象的形态取决于目标事件。如果目标事件是原生 DOM 元素事件,$event
就是DOM事件对象,它有像target
和target.value
这样的属性。
考虑这个范例:
src/app/app.component.htmlcontent_copy<input [value]"currentHero.name" (input)"currentHero.name=$event.target.value" >
上面的代码在把输入框的value
属性绑定到firstName
属性。 要监听对值的修改,代码绑定到输入框的input
事件。 当用户造成更改时,input
事件被触发,并在包含了 DOM 事件对象 ($event
) 的上下文中执行这条语句。
要更新firstName
属性,就要通过路径$event.target.value
来获取更改后的值。
如果事件属于指令(回想一下,组件是指令的一种),那么$event
具体是什么由指令决定。
使用EventEmitter实现自定义事件
通常,指令使用 AngularEventEmitter来触发自定义事件。 指令创建一个EventEmitter
实例,并且把它作为属性暴露出来。 指令调用EventEmitter.emit(payload)
来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过$event
对象来访问载荷。
假设HeroDetailComponent
用于显示英雄的信息,并响应用户的动作。 虽然HeroDetailComponent
包含删除按钮,但它自己并不知道该如何删除这个英雄。 最好的做法是触发事件来报告“删除用户”的请求。
下面的代码节选自HeroDetailComponent
:
content_copytemplate: ` <div> <img src="{{heroImageUrl}}"> <span [style.text-decoration]="lineThrough"> {{prefix}} {{hero?.name}} </span> <button (click)="delete()">Delete</button> </div>`src/app/hero-detail.component.ts (deleteRequest)
content_copy// This component makes a request but it can't actually delete a hero. deleteRequest = new EventEmitter<Hero>(); delete() { this.deleteRequest.emit(.hero); }
组件定义了deleteRequest
属性,它是EventEmitter
实例。 当用户点击删除时,组件会调用delete()
方法,让EventEmitter
发出一个Hero
对象。
现在,假设有个宿主的父组件,它绑定了HeroDetailComponent
的deleteRequest
事件。
content_copy<app-hero-detail (deleteRequest)"deleteHero($event)" [hero]"currentHero"></app-hero-detail>
当deleteRequest
事件触发时,Angular 调用父组件的deleteHero
方法, 在$event
变量中传入要删除的英雄(来自HeroDetail
)。
模板语句有副作用
deleteHero
方法有副作用:它删除了一个英雄。 模板语句的副作用不仅没问题,反而正是所期望的。
ThedeleteHero
method has a side effect: it deletes a hero. Template statement side effects are not just OK,but expected.
删除这个英雄会更新模型,还可能触发其它修改,包括向远端服务器的查询和保存。 这些变更通过系统进行扩散,并最终显示到当前以及其它视图中。
双向数据绑定 ([(...)])
在元素层面上,既要设置元素属性,又要监听元素事件变化。
Angular 为此提供一种特殊的双向数据绑定语法:[(x)]
。[(x)]
语法结合了属性绑定的方括号[x]
和事件绑定的圆括号(x)
。
想象盒子里的香蕉来记住方括号套圆括号。
当一个元素拥有可以设置的属性x
和对应的事件xChange
时,解释[(x)]
语法就容易多了。 下面的SizerComponent
符合这个模式。它有size
属性和伴随的sizeChange
事件:
content_copy
- import { Component, EventEmitter Input Output} from '@angular/core';
- @Component({
- selector:'app-sizer'`
- <div>
- <button (click)="dec()" title="smaller">-</button>
- <button (click)="inc()" title="bigger">+</button>
- <label [style.font-size.px]="size">FontSize: {{size}}px</label>
- </div>`
- })
- exportclass SizerComponent{
- @Input() size number |string;
- @Output sizeChange =new EventEmitter<number>();
- dec.resize(-1);}
- inc(+}
- resize(delta number){
- size Mathmin(40max8++ delta));
- sizeChangeemitsize);
- }
- }
size的初始值是一个输入值,来自属性绑定。(译注:注意size
前面的@Input
) 点击按钮,在最小/最大值范围限制内增加或者减少size
。 然后用调整后的size
触发sizeChange
事件。
下面的例子中,AppComponent.fontSize
被双向绑定到SizerComponent
:
content_copy<app-sizer [(size)]"fontSizePx"></app-sizer> px]>Resizable TextSizerComponent.size初始值是AppComponent.fontSizePx
。 点击按钮时,通过双向绑定更新AppComponent.fontSizePx
。 被修改的AppComponent.fontSizePx
通过样式绑定,改变文本的显示大小。双向绑定语法实际上是属性绑定和事件绑定的语法糖。 Angular将
src/app/app.component.html (two-way-2)SizerComponent
的绑定分解成这样:content_copy<app-sizer [size]"fontSizePx" (sizeChange)"fontSizePx=$event"></app-sizer>$event变量包含了
SizerComponent.sizeChange
事件的荷载。 当用户点击按钮时,Angular 将$event
赋值给AppComponent.fontSizePx
。显然,比起单独绑定属性和事件,双向数据绑定语法显得非常方便。
我们希望能在像
<input>
和<select>
这样的 HTML 元素上使用双向数据绑定。 可惜,原生 HTML 元素不遵循x
值和xChange
事件的模式。幸运的是,Angular 以NgModel指令为桥梁,允许在表单元素上使用双向数据绑定。
内置指令上一版本的 Angular 中包含了超过 70 个内置指令。 社区贡献了更多,这还没算为内部应用而创建的无数私有指令。
在新版的 Angular 中不需要那么多指令。 使用更强大、更富有表现力的 Angular 绑定系统,其实可以达到同样的效果。 如果能用简单的绑定达到目的,为什么还要创建指令来处理点击事件呢?
src/app/app.component.htmlcontent_copy我们仍然可以从简化复杂任务的指令中获益。 Angular 发布时仍然带有内置指令,只是没那么多了。 我们仍会写自己的指令,只是没那么多了。下面来看一下那些最常用的内置指令。它们可分为属性型指令或结构型指令。
内置属性型指令属性型指令会监听和修改其它HTML元素或组件的行为、元素属性(Attribute)、DOM属性(Property)。 它们通常会作为HTML属性的名称而应用在元素上。
更多的细节参见属性型指令一章。 很多Angular模块,比如
RouterModule
和FormsModule都定义了自己的属性型指令。 本节将会介绍几个最常用的属性型指令:
NgClass 指令
我们经常用动态添加或删除 CSS 类的方式来控制元素如何显示。 通过绑定到NgClass
,可以同时添加或移除多个类。
content_copy当想要同时添加或移除多个CSS 类时,NgClass
指令可能是更好的选择。试试把
ngClass
绑定到一个 key:value 形式的控制对象。这个对象中的每个 key 都是一个 CSS 类名,如果它的 value 是true
,这个类就会被加上,否则就会被移除。组件方法
src/app/app.component.tssetCurrentClasses
可以把组件的属性currentClasses
设置为一个对象,它将会根据三个其它组件的状态为true
或false
而添加或移除三个类。content_copycurrentClasses: {}; setCurrentClasses{ // CSS classes: added/removed per current state of component properties .currentClasses = { 'saveable': .canSave 'modified'!.isUnchanged'special'.isSpecial }; 把NgClass
属性绑定到currentClasses
,根据它来设置此元素的CSS类:src/app/app.component.htmlcontent_copy<div [ngClass]"currentClasses">This div is initially saveable,unchanged,and special