学习如何编写显示数据并在数据绑定的帮助下使用用户事件的模板。
Angular应用程序管理用户看到和可以做的事情,通过组件类实例(组件)和面向用户的模板的交互来实现这一点。
您可以熟悉模型 - 视图 - 控制器(MVC)或模型 - 视图 - 视图模型(MVVM)的组件/模板。 在Angular中,组件扮演控制器/视图模型的一部分,模板表示视图。
内容
本指南涵盖了Angular模板语法的基本元素,以及构建视图所需的元素:
- 模板中的HTML
- 插值({{...}})
- 模板表达式
- 模板语句
- 绑定语法
- 属性绑定([property])
- 属性,类和样式绑定
- 事件绑定((event))
- 双向数据绑定([(...)])
- 内置指令
- 内置的属性指令
- NgClass
- NgStyle
- NgModel ([(ngModel)])
- 内置结构指令
- NgIf
- NgFor
- 模板输入变量
- Microsyntax
- NgSwitch指令
- 模板引用变量(#var)
- 输入和输出属性(@Input和@Output)
- 模板表达式运算符
- 管道(|)
- 安全导航操作员(?.)
现成示例(查看源代码)演示了本指南中描述的所有语法和代码片段。
模板中的HTML
HTML是Angular模板的语言。 几乎所有的HTML语法都是有效的模板语法。 <script>元素是一个值得注意的例外。 这是被禁止的,消除脚本注入攻击的风险。 在实践中,<script>被忽略,并在浏览器控制台中出现警告。 有关详情,请参阅安全性页面。
一些合法的HTML在模板中没有多大意义。 <html>,<body>和<base>元素没有用处。 剩下一切都是一致的。
您可以使用组件和指令出现的新元素和属性来扩展模板的HTML词汇表。 在下面的章节中,您将学习如何通过数据绑定来动态获取和设置DOM(文档对象模型)值。
从数据绑定插值的第一种形式开始,看看有多少更丰富的模板HTML可以使用。请回到顶部。
插值({{...}})
在Angular的早期教程中,你遇到了插值的双曲括号{{and}}。
<p>My current hero is {{currentHero.name}}</p>
您可以使用插值将计算的字符串组织到HTML元素标记和属性赋值之间的文本中。
<h3>
{{title}}
<img src="{{heroImageUrl}}" style="height:30px">
</h3>
大括号里的文本通常是组件属性的名称。 Angular用相应的属性值替换该名称。 在上面的例子中,Angular评估了title和heroImageUrl属性,并“填充空白”,首先直接显示一个应用标题,然后是一个英雄图像。
更多的,大括号之间的文本是一个模板表达式,Angular首先评估并转换为一个字符串, 通过添加这两个数字来进行以下内插:
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>
该表达式可以调用主机的方法,例如getVal(),如下所示:
<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
Angular用双曲花括号评估所有表达式,将表达式结果转换为字符串,并将它们与相邻的文字串相链接。最后,它将这个复合插值结果赋值给一个元素或指令属性
您似乎在元素标记之间插入结果并将其分配给属性。这么想很方便,你会因为这个错误而受苦。虽然这不完全正确。插值是收敛到属性绑定中的一种特殊语法,如下所述。
但首先,让我们仔细看看模板表达式和语句。
模板表达式
模板表达式产生一个值。 Angular执行表达式并将其分配给绑定目标的属性; 目标可能是HTML元素,组件或指令。
{{1 + 1}}中的内插大括号包围模板表达式1 + 1.在下面的属性绑定部分中,在[property] =“expression”中,模板表达式显示在符号右侧的引号中。
你用类似Dart的语言编写这些模板表达式。 许多Dart表达式是合法的,但不是全部。
带有或促进副作用的Dart表达式是被禁止的,包括:
- 赋值(=,+ =, - =,...)
- new 或 const
- 链接表达式;
- 递增和递减运算符(++和 - -)
与Dart语法的其他显着差异包括:
不支持Dart字符串插值; 例如,而不是“'The title is $title'”,你必须写''The title is ' + title'“
不支持按位运算符| 和&
新的模板表达式运算符,如|
表达式上下文
表达式上下文通常是组件实例。 在以下片段中,双花括号内的标题和引号中的isUnchanged引用了AppComponent的属性。
{{title}}
<span [hidden]="isUnchanged">changed</span>
一个表达式也可以用来引用模板上下文的属性,包括模板输入变量(let hero)或模板引用变量(#heroInput)。
<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}
表达式中术语的上下文是模板变量和组件成员的混合。 如果引用这些名称空间的名称,则模板变量名称优先,后面是指令的上下文,最后是组件的成员名称。
前面的例子显示了这样一个名字冲突。 该组件具有hero属性,而* ngFor定义了英雄模板变量。 {{hero.name}}中的英雄是指变量输入变量,而不是组件的属性。
模板表达式不能引用静态属性,也不能引用顶层变量或函数,如来自dart:html的window 或document 。他们不能直接调用从dart:math导入的print或函数。 它们仅限于引用表达式上下文的成员。
表达式准则
模板表达式可以构建或破坏应用程序。 请遵循以下准则:
这些指导方针的例外情况应该是在你理解的情况下。
没有明显的副作用
模板表达式不应该更改目标属性的值以外的任何应用程序状态。
这个规则对Angular的“单向数据流”策略是必不可少的。您不必担心读取组件值可能会改变一些其他的显示值。这个视图在整个渲染过程中应该是稳定的。
快速执行
Angular在每个更改检测周期后执行模板表达式。 更改检测周期由许多异步活动触发,如承诺的分辨率,http结果,计时器事件,按键和鼠标移动。
表达式应该快速完成,否则用户可能会遇到卡帧,尤其是在较慢的设备上。 当他们的计算成本很高时,考虑缓存值。
简单
虽然可以编写相当复杂的模板表达式,但是应该避免使用它们。
属性名称或方法调用应该是标准。 偶尔的布尔否定(!)可以。
另外, 将应用和业务逻辑放到到组件本身,在那里它将更容易开发和测试。
幂等性
幂等表达式是理想的,因为它没有副作用,并且改善了Angular的变化检测性能。
对Angular来说,一个幂等表达式总是返回完全相同的东西,直到它的一个依赖值发生变化。
在事件循环的一个回合期间,依赖值不应该改变。如果一个幂等表达式返回一个字符串或一个数字,当它在一行中调用两次时会返回相同的字符串或数字。如果表达式返回一个对象(包括一个List),它将在连续调用两次时返回相同的对象引用。
模板语句
模板语句响应绑定目标(例如元素,组件或指令)引发的事件。 您会在事件绑定部分看到模板语句,并在(event)=“statement”中出现在=符号右侧的引号中。
<button (click)="deleteHero()">Delete hero</button>
模板语句有一方面的作用。 它是一个事件的全部。 就是如何从用户操作更新应用程序状态。
响应事件是Angular的“单向数据流”的另一面。在事件循环的这个周期中,您可以自由地在任何地方进行所有更改。
像模板表达式一样,模板语句使用了一种看起来像Dart的语言。 模板语句解析器与模板表达式解析器不同,特别支持基本的赋值(=)和链接表达式(with;)
但是,某些Dart语法是不允许的:
- new 和 const
- 递增和递减运算符,++和 --
- 赋值运算符,例如 +=和 -=
- 按位运算符| 和 &
- 模板表达式运算符
语句上下文
与表达式一样,语句只能引用语句上下文中的内容,例如组件实例的事件处理方法。
语句上下文通常是组件实例。 (click)=“deleteHero()”中的deleteHero是数据绑定组件的一种方法。
<button (click)="deleteHero()">Delete hero</button>
语句上下文也可以引用模板自己的上下文的属性。 在以下示例中,将模板$ event对象,模板输入变量(let hero)和模板引用变量(#heroForm)传递给组件的事件处理方法。
<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
模板上下文字段优先于组件上下文字段。 在上面的deleteHero(hero)中,hero是模板输入变量,而不是组件的hero属性。
模板语句不能引用类的静态属性,也不能引用顶层变量或函数,如来自dart:html的window或document 。 它们不能直接调用从dart:math导入的print或函数。
声明准则
与表达式一样,避免编写复杂的模板语句。 方法调用或简单的属性分配应该是标准。
现在您已经感觉到了模板表达式和语句,您已经准备好了解超越插值的各种数据绑定语法。
绑定语法:概述
数据绑定是一种协调用户看到应用程序数据值的机制。 虽然您可以将值推送到HTML中,并从HTML中提取值,但是如果将这些杂事转换为绑定框架,则应用程序更易于编写,读取和维护。 您只需声明绑定源和目标HTML元素之间的绑定,然后让框架完成工作。
Angular提供了多种数据绑定。 本指南涵盖了大部分的Angular数据绑定及其语法的高级使用。
绑定类型可以按照数据流的方向分为三类:source-to-view,view-to-source,以及双向顺序:view-to-source-to-view:
数据方向 | 语法 | 类型 |
---|---|---|
单向 从数据源到目标视图 |
| 插值 组件属性 元素属性 元素类 元素样式 |
单向 从目标视图到数据源 |
| 事件 |
双向 |
| 双向 |
除插值以外的绑定类型在等号左边或者用标点符号([],())包围,或者带前缀(bind-,on-,bindon-)都有一个目标名称。
目标名称是一个属性的名称。 它可能看起来像一个元素属性的名称,但它不是。 为了体会差异性,您必须开发一种思考HTML模板的新方法。
一种新的心智模式
借助数据绑定的所有功能以及使用自定义标记扩展HTML词汇表的能力,将HTML模板视为HTML Plus是很有诱惑力的
它确实是HTML Plus。 但是它也与你习惯的HTML有很大的不同。 它需要一个新的心智模式。
在HTML开发的正常过程中,您可以使用HTML元素创建一个可视结构,并通过使用字符串常量设置元素属性来修改这些元素。
<div class="special">Mental Model</div>
<img src="assets/images/hero.png">
<button disabled>Save</button>
您仍然在Angular模板中以这种方式创建结构并初始化属性值。
然后,您将学习如何使用封装了HTML的组件创建新元素,并将它们放入模板中,就好像它们是原生HTML元素一样。
<!-- Normal HTML -->
<div class="special">Mental Model</div>
<!-- Wow! A new element! -->
<hero-detail></hero-detail>
这是HTML Plus。
然后你学习数据绑定。 你遇到的第一个绑定可能是这样的:
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>
你的直觉可能表明你绑定了按钮的disabled属性,并将其值设置为组件的isUnchanged属性的当前值。 那个直觉是不正确的!
日常的HTML心智模式是误导性的。 一旦你开始数据绑定,你不再使用HTML Attributes 。 你不是设置属性(Attributes) ; 你应该设置DOM元素,组件和指令的属性(Properties)。
HTML属性(Attributes)与DOM属性(Properties)
HTML属性和DOM属性的区别对于理解Angular绑定是如何工作是至关重要的。
Attributes 由HTML定义。Properties 由DOM(文档对象模型)定义。
- 一些HTML属性(Attributes)映射到属性(Properties)1:1, id是一个例子。
- 一些HTML属性(Attributes)没有相应的属性(Properties)。 colspan就是一个例子。
- 一些DOM属性(Properties)没有相应的属性(Attributes)。 textContent就是一个例子。
- 许多HTML属性(Attributes)似乎映射到属性(Properties)...但不是以你想象的方式!
最后一个类别含义模糊的,除非你知道这个一般规则:
属性(Attributes)初始化DOM属性(Properties),然后完工。 属性(Properties)值可以会改变; 属性(Attributes)值不能。
例如,当浏览器呈现<input type =“text” value =“Bob”>时,它会创建一个对应的DOM节点,其值属性(Properties)已初始化为“Bob”。
当用户在输入框中输入“Sally”时,DOM元素值属性变为“Sally”。 但是,HTML value属性保持不变,当访问输入元素的该属性:input.getAttribute('value')返回“Bob”。
HTML属性(Attributes) value指定初始值; DOM value属性(Properties)是当前值。
disabled 属性(Attributes)是另一个特殊的例子。 按钮的disabled 属性(Properties)默认为false,因此按钮已启用。 当您添加disabled属性(Attributes)时,它的存在会将按钮的disabled属性(Properties)初始化为true,因此该按钮被禁用。
添加和删除disabled属性(Attributes)将禁用和启用该按钮。
该属性(Attributes)的值是无关紧要的,这就是为什么您不能通过编写<button disabled =“false”> Still Disabled </ button>来启用按钮的原因。设置按钮的disabled属性(Properties)(例如,使用Angular绑定)禁用或启用按钮。属性(Properties)的值很重要。
HTML属性(Attributes)和DOM属性(Properties)是不一样的,即使它们具有相同的名称。
这个事实值得重复:模板绑定使用属性(properties)和事件(events)发挥作用,而不是属性(attributes)。
一个没有属性的世界
在Angular的世界中,属性(attributes)的唯一作用是初始化元素和指令状态。 当你写数据绑定时,你只处理目标对象的属性(properties)和事件(events)。 HTML属性(attributes)不起作用。
记住这个模型,继续阅读以了解绑定目标。
绑定目标
数据绑定的目标是DOM中的东西。根据绑定类型,目标可以是(element | component |directive)属性,(element | component | directive)事件或(很少)属性(attributes)名称。 下表总结了这些情况:
Type | Target | Example |
---|---|---|
Property | Element property Component property Directive property |
|
Event | Element event Component event Directive event |
|
Two-way | Event and property |
|
Attribute | Attribute (例外) |
|
Class | class property |
|
Style | style property |
|
您现在已经准备好详细查看绑定类型。
属性绑定([property])
编写一个模板属性绑定来设置一个视图元素的属性。 该绑定将该属性设置为模板表达式的值。
最常见的属性绑定将元素属性设置为组件属性值。一个示例是将图像元素的src属性绑定到组件的heroImageUrl属性:
<img [src]="heroImageUrl">
另一个例子是当组件标识isUnchanged的时候禁用一个按钮:
<button [disabled]="isUnchanged">Cancel is disabled</button>
另一个是设置一个指令的属性:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
另一个是设置自定义组件的模型属性(父组件和子组件进行通信的一个好方法):
<hero-detail [hero]="currentHero"></hero-detail>
单向
人们通常将属性绑定描述为单向数据绑定,因为它从一个组件的数据属性向一个目标元素属性传递一个值。
您不能使用属性绑定将值从目标元素中拉出。 您不能绑定到目标元素的属性来读取它。 你只能设置它。
同样,您不能在目标元素上使用属性绑定来调用方法。
如果元素引发事件,则可以使用事件绑定来监听它们。
如果您必须读取目标元素属性或调用其中一个方法, 你需要一个不同的技术。 查看ViewChild和ContentChild的API参考。
绑定目标
方括号之间的元素属性标识目标属性。 以下代码中的目标属性是图像元素的src属性。
<img [src]="heroImageUrl">
有些人更喜欢绑定前缀bind-替代,称为规范形式:
<img bind-src="heroImageUrl">
目标名称始终是属性(property)的名称,即使它看起来是别的名称。 你可能会看到src,并认为它是一个属性(attribute)的名称。 不是; 这是一个图像元素属性(property)的名称。
元素属性(property)可能是更常见的目标,但Angular首先查看名称是否是已知指令的属性(property),如下例所示:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
从技术上讲,Angular将名称与指令输入或用@Input()装饰的属性相匹配。 这样的输入映射到指令自己的属性。
如果名称未能匹配已知指令或元素(property)的属性,则Angular会报告“未知指令”错误。
避免副作用
如前所述,模板表达式的评估必须没有可见的副作用。表达式语言本身是为了保证您的安全。您不能为属性绑定表达式中的任何东西赋值,也不能使用增量和减量运算符。
当然,该表达式可能会调用具有副作用的属性或方法。 Angular无法知道或阻止你。
该表达式可以调用类似getFoo()的东西。 只要你知道getFoo()是做什么的。如果getFoo()改变了某些东西,而且碰巧绑定了某些东西,你将冒着一定的风险。Angular可能会或可能不会显示更改的值。Angular可能会检测到更改并发出警告错误。通常来说,保留数据属性和方法返回值就够了。
返回适当的类型
模板表达式应通过目标属性计算预期值的类型:
- 如果目标属性需要一个字符串,则返回一个字符串。
- 如果目标属性期望一个数字,则返回一个数字。
- 如果目标属性需要一个对象,则返回一个对象。
HeroDetail组件的hero属性需要一个Hero对象,这正是你在属性绑定中发送的内容:
<hero-detail [hero]="currentHero"></hero-detail>
检查模式异常
在检查模式下,如果模板表达结果类型和目标属性类型不是赋值兼容的,则会抛出一个类型异常。 有关检查模式的信息,请参阅Dart语言指南中的重要概念。
Dart 2.0注意:检查模式不会出现在飞镖2.0。 有关更多信息,请参阅Dart 2.0更新。
记住括号
括号告诉Angular评估模板表达式。 如果省略方括号,Angular会将该字符串视为常量,并使用该字符串初始化目标属性。 它不评估字符串!
不要犯以下错误:
<!-- ERROR: A value of type 'String' can't be assigned to a variable of type 'Hero'.
<hero-detail hero="currentHero"></hero-detail>
-->
检查模式类型异常例子
在检查模式下,上面的代码将导致一个类型异常:String不是Hero的子类型。
一次性字符串初始化
满足以下所有条件时,省略括号:
- 目标属性接受一个字符串值。
- 该字符串是一个固定的值,您可以拷贝到模板中。
- 这个初始值永远不会改变。
一次性字符串初始化在标准HTML中是常规的,并且它对于指令和组件属性也同样适用。 以下示例将HeroDetailComponent的prefix属性初始化为固定字符串,而不是模板表达式。 Angular设置它并不再管它。
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>
另一方面,[hero]绑定仍然保留对组件的currentHero属性的有效绑定。
属性绑定或插值?
你经常有插值和属性绑定的选择。 以下绑定做同样的事情:
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
在许多情况下插值是属性绑定较为方便的替代品。
将数据值呈现为字符串时,没有技术上的理由去选择另一种形式,但插值更可读。我们建议建立编码风格规则,选择符合规则的形式,对于手头的任务来说是最自然的
将元素属性设置为非字符串数据值时,必须使用属性绑定。
内容安全
想象下面的恶意内容。
String evilTitle =
'Template <script>alert("evil never sleeps")</script>Syntax';
幸运的是,Angular数据绑定对危险的HTML进行了警报。 它在显示它们之前清理这些值。 它不允许带脚本标记的HTML泄露到浏览器中,既不能使用插值也不能使用属性绑定。
<!--
Angular generates warnings for these two lines as it sanitizes them
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
-->
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
插值处理脚本标记与属性绑定不同,但两种方法均无害地呈现内容。
属性(Attribute),类和样式绑定
模板语法为不太适合属性(property )绑定的场景提供了专门的单向绑定。
属性(Attribute)绑定
您可以直接使用属性绑定来设置属性的值。
这是绑定设置目标属性(property)的唯一例外规则。 这是创建和设置属性(attribute)的唯一一种绑定。
本指南反复强调,使用属性(property)绑定设置元素属性(property)始终优先于使用字符串设置属性(attribute)。 Angular为什么提供属性(attribute)绑定?
当没有要绑定的元素属性时,必须使用属性绑定。
考虑ARIA,SVG和table span属性。 他们是纯粹的属性。 它们不对应元素属性,也不设置元素属性。 没有属性目标绑定。
在写像这样的东西时,这个事实变得非常明显:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
结果是这个错误:
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property
正如消息所述,<td>元素没有colspan属性。 它具有“colspan”属性(attribute),但是插值和属性(attribute)绑定只能设置属性(properties),而不能设置属性(attribute)。
您需要属性(attribute)绑定来创建和绑定到这些属性(attribute)。
属性(attribute)绑定语法类似于属性(properties)绑定。以前缀attr开头,后跟一个点(.)和属性名称代替括号之间的元素属性。然后使用解析为字符串的表达式来设置属性值。
将[attr.colspan]绑定到计算值:
<table border=1>
<!-- 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>
以下是表格的呈现方式:
属性(attribute)绑定的主要用例之一是设置ARIA属性,如下例所示:
<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>