The Angular application manages what the user sees and can do, achieving this through the interaction of a
component class instance (the component) and its user-facing template.
You may be familiar with the component/template duality from your experience with model-view-controller (MVC) or model-view-viewmodel (MVVM).
In Angular, the component plays the part of the controller/viewmodel, and the template represents the view.
目录
Contents
本章涵盖了Angular模板语法中的基本元素,你在构建视图时会用到它们:
This guide covers the basic elements of the Angular template syntax, elements you'll need to construct the view:
HTML 是 Angular 模板的语言。几乎所有的HTML语法都是有效的模板语法。
但值得注意的例外是<script>元素,它被禁用了,以阻止脚本注入攻击的风险。(实际上,<script>只是被忽略了。)
参见安全页了解详情。
HTML is the language of the Angular template.
Almost all HTML syntax is valid template syntax.
The <script> element is a notable exception;
it is forbidden, eliminating the risk of script injection attacks.
In practice, <script> is ignored and a warning appears in the browser console.
See the Security page for details.
有些合法的 HTML 被用在模板中是没有意义的。<html>、<body>和<base>元素这个舞台上中并没有扮演有用的角色。剩下的所有元素基本上就都一样用了。
Some legal HTML doesn't make much sense in a template.
The <html>, <body>, and <base> elements have no useful role.
Pretty much everything else is fair game.
可以通过组件和指令来扩展模板中的 HTML 词汇。它们看上去就是新元素和属性。接下来将学习如何通过数据绑定来动态获取/设置 DOM(文档对象模型)的值。
You can extend the HTML vocabulary of your templates with components and directives that appear as new elements and attributes.
In the following sections, you'll learn how to get and set DOM (Document Object Model) values dynamically through data binding.
我们首先看看数据绑定的第一种形式 —— 插值表达式,它展示了模板的 HTML 可以有多丰富。
Begin with the first form of data binding—interpolation—to see how much richer template HTML can be.
The text between the braces is often the name of a component property. Angular replaces that name with the
string value of the corresponding component property. In the example above, Angular evaluates the title and heroImageUrl properties
and "fills in the blanks", first displaying a bold application title and then a heroic image.
More generally, the text between the braces is a template expression that Angular first evaluates
and then converts to a string. The following interpolation illustrates the point by adding the two numbers:
<!-- "The sum of 1 + 1 is 2" --><p>The sum of 1 + 1 is {{1 + 1}}</p>
这个表达式可以调用宿主组件的方法,就像下面用的getVal():
The expression can invoke methods of the host component such as getVal(), seen here:
<!-- "The sum of 1 + 1 is not 4" --><p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
Angular evaluates all expressions in double curly braces,
converts the expression results to strings, and links them with neighboring literal strings. Finally,
it assigns this composite interpolated result to an element or directive property.
You appear to be inserting the result between element tags and assigning it to attributes.
It's convenient to think so, and you rarely suffer for this mistake.
Though this is not exactly true. Interpolation is a special syntax that Angular converts into a
property binding, as is explained below.
讲解属性绑定之前,先深入了解一下模板表达式和模板语句。
But first, let's take a closer look at template expressions and statements.
模板表达式产生一个值。
Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。
A template expression produces a value.
Angular executes the expression and assigns it to a property of a binding target;
the target might be an HTML element, a component, or a directive.
The interpolation braces in {{1 + 1}} surround the template expression 1 + 1.
In the property binding section below,
a template expression appears in quotes to the right of the = symbol as in [property]="expression".
You write these template expressions in a language that looks like JavaScript.
Many JavaScript expressions are legal template expressions, but not all.
JavaScript 中那些具有或可能引发副作用的表达式是被禁止的,包括:
JavaScript expressions that have or promote side effects are prohibited,
including:
赋值 (=, +=, -=, ...)
assignments (=, +=, -=, ...)
new运算符
new
使用;或,的链式表达式
chaining expressions with ; or ,
自增或自减操作符 (++和--)
increment and decrement operators (++ and --)
和 JavaScript语 法的其它显著不同包括:
Other notable differences from JavaScript syntax include:
The expression context is typically the component instance.
In the following snippets, the title within double-curly braces and the
isUnchanged in quotes refer to properties of the AppComponent.
The context for terms in an expression is a blend of the template variables,
the directive's context object (if it has one), and the component's members.
If you reference a name that belongs to more than one of these namespaces,
the template variable name takes precedence, followed by a name in the directive's context,
and, lastly, the component's member names.
The previous example presents such a name collision. The component has a hero
property and the *ngFor defines a hero template variable.
The hero in {{hero.name}}
refers to the template input variable, not the component's property.
Template expressions cannot refer to anything in
the global namespace. They can't refer to window or document. They
can't call console.log or Math.max. They are restricted to referencing
members of the expression context.
This rule is essential to Angular's "unidirectional data flow" policy.
You should never worry that reading a component value might change some other displayed value.
The view should be stable throughout a single rendering pass.
Angular executes template expressions after every change detection cycle.
Change detection cycles are triggered by many asynchronous activities such as
promise resolutions, http results, timer events, keypresses and mouse moves.
Expressions should finish quickly or the user experience may drag, especially on slower devices.
Consider caching values when their computation is expensive.
非常简单
Simplicity
虽然也可以写出相当复杂的模板表达式,但不要那么写。
Although it's possible to write quite complex template expressions, you should avoid them.
A property name or method call should be the norm.
An occasional Boolean negation (!) is OK.
Otherwise, confine application and business logic to the component itself,
where it will be easier to develop and test.
Dependent values should not change during a single turn of the event loop.
If an idempotent expression returns a string or a number, it returns the same string or number
when called twice in a row. If the expression returns an object (including an array),
it returns the same object reference when called twice in a row.
模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。
模板语句将在事件绑定一节看到,它出现在=号右侧的引号中,就像这样:(event)="statement"。
A template statement responds to an event raised by a binding target
such as an element, component, or directive.
You'll see template statements in the event binding section,
appearing in quotes to the right of the = symbol as in (event)="statement".
Responding to events is the other side of Angular's "unidirectional data flow".
You're free to change anything, anywhere, during this turn of the event loop.
Like template expressions, template statements use a language that looks like JavaScript.
The template statement parser differs from the template expression parser and
specifically supports both basic assignment (=) and chaining expressions
(with ; or ,).
然而,某些 JavaScript 语法仍然是不允许的:
However, certain JavaScript syntax is not allowed:
The statement context may also refer to properties of the template's own context.
In the following examples, the template $event object,
a template input variable (let hero),
and a template reference variable (#heroForm)
are passed to an event handling method of the component.
Template context names take precedence over component context names.
In deleteHero(hero) above, the hero is the template input variable,
not the component's hero property.
Now that you have a feel for template expressions and statements,
you're ready to learn about the varieties of data binding syntax beyond interpolation.
数据绑定是一种机制,用来协调用户所见和应用数据。
虽然我们能往 HTML 推送值或者从 HTML 拉取值,
但如果把这些琐事交给数据绑定框架处理,
应用会更容易编写、阅读和维护。
只要简单地在绑定源和目标 HTML 元素之间声明绑定,框架就会完成这项工作。
Data binding is a mechanism for coordinating what users see, with application data values.
While you could push values to and pull values from HTML,
the application is easier to write, read, and maintain if you turn these chores over to a binding framework.
You simply declare bindings between binding sources and target HTML elements and let the framework do the work.
Binding types can be grouped into three categories distinguished by the direction of data flow:
from the source-to-view, from view-to-source, and in the two-way sequence: view-to-source-to-view:
Binding types other than interpolation have a target name to the left of the equal sign,
either surrounded by punctuation ([], ()) or preceded by a prefix (bind-, on-, bindon-).
The target name is the name of a property. It may look like the name of an attribute but it never is.
To appreciate the difference, you must develop a new way to think about template HTML.
新的思维模型
A new mental model
数据绑定的威力和允许用自定义标记扩展 HTML 词汇的能力,容易误导我们把模板 HTML 当成 HTML+。
With all the power of data binding and the ability to extend the HTML vocabulary
with custom markup, it is tempting to think of template HTML as HTML Plus.
它其实就是 HTML+。
但它也跟我们熟悉的 HTML 有着显著的不同。
我们需要一种新的思维模型。
It really is HTML Plus.
But it's also significantly different than the HTML you're used to.
It requires a new mental model.
在正常的 HTML 开发过程中,我们使用 HTML 元素创建视觉结构,
通过把字符串常量设置到元素的 attribute 来修改那些元素。
In the normal course of HTML development, you create a visual structure with HTML elements, and
you modify those elements by setting element attributes with string constants.
You'll get to that peculiar bracket notation in a moment. Looking beyond it,
your intuition suggests that you're binding to the button's disabled attribute and setting
it to the current value of the component's isUnchanged property.
但我们的直觉是错的!日常的 HTML 思维模式在误导我们。
实际上,一旦开始数据绑定,就不再跟 HTML attribute 打交道了。
这里不是设置 attribute,而是设置 DOM 元素、组件和指令的 property。
Your intuition is incorrect! Your everyday HTML mental model is misleading.
In fact, once you start data binding, you are no longer working with HTML attributes. You aren't setting attributes.
You are setting the properties of DOM elements, components, and directives.
HTML attribute 与 DOM property 的对比
HTML attribute vs. DOM property
要想理解 Angular 绑定如何工作,重点是搞清 HTML attribute 和 DOM property 之间的区别。
The distinction between an HTML attribute and a DOM property is crucial to understanding how Angular binding works.
attribute 是由 HTML 定义的。property 是由 DOM (Document Object Model) 定义的。
Attributes are defined by HTML. Properties are defined by the DOM (Document Object Model).
少量 HTML attribute 和 property 之间有着 1:1 的映射,如id。
A few HTML attributes have 1:1 mapping to properties. id is one example.
有些 HTML attribute 没有对应的 property,如colspan。
Some HTML attributes don't have corresponding properties. colspan is one example.
有些 DOM property 没有对应的 attribute,如textContent。
Some DOM properties don't have corresponding attributes. textContent is one example.
大量 HTML attribute看起来映射到了property…… 但却不像我们想的那样!
Many HTML attributes appear to map to properties ... but not in the way you might think!
最后一类尤其让人困惑…… 除非我们能理解这个普遍原则:
That last category is confusing until you grasp this general rule:
attribute 初始化 DOM property,然后它们的任务就完成了。property 的值可以改变;attribute 的值不能改变。
Attributes initialize DOM properties and then they are done.
Property values can change; attribute values can't.
例如,当浏览器渲染<input type="text" value="Bob">时,它将创建相应 DOM 节点,
其value property 被初始化为 “Bob”。
For example, when the browser renders <input type="text" value="Bob">, it creates a
corresponding DOM node with a value property initialized to "Bob".
When the user enters "Sally" into the input box, the DOM element valueproperty becomes "Sally".
But the HTML valueattribute remains unchanged as you discover if you ask the input element
about that attribute: input.getAttribute('value') returns "Bob".
HTML attribute value指定了初始值;DOM value property 是当前值。
The HTML attribute value specifies the initial value; the DOM value property is the current value.
The disabled attribute is another peculiar example. A button's disabledproperty is
false by default so the button is enabled.
When you add the disabledattribute, its presence alone initializes the button's disabledproperty to true
so the button is disabled.
Adding and removing the disabledattribute disables and enables the button. The value of the attribute is irrelevant,
which is why you cannot enable a button by writing <button disabled="false">Still Disabled</button>.
In the world of Angular, the only role of attributes is to initialize element and directive state.
When you write a data binding, you're dealing exclusively with properties and eventsof the target object.
HTML attributes effectively disappear.
把这个思维模型牢牢的印在脑子里,接下来,学习什么是绑定目标。
With this model firmly in mind, read on to learn about binding targets.
The target of a data binding is something in the DOM.
Depending on the binding type, the target can be an
(element | component | directive) property, an
(element | component | directive) event, or (rarely) an attribute name.
The following table summarizes:
The most common property binding sets an element property to a component property value. An example is
binding the src property of an image element to a component's heroImageUrl property:
<img [src]="heroImageUrl">
另一个例子是当组件说它isUnchanged(未改变)时禁用按钮:
Another example is disabling a button when the component says that it isUnchanged:
<button [disabled]="isUnchanged">Cancel is disabled</button>
另一个例子是设置指令的属性:
Another is setting a property of a directive:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
还有另一个例子是设置自定义组件的模型属性(这是父子组件之间通讯的重要途径):
Yet another is setting the model property of a custom component (a great way
for parent and child components to communicate):
<hero-detail [hero]="currentHero"></hero-detail>
单向输入
One-way in
人们经常把属性绑定描述成单向数据绑定,因为值的流动是单向的,从组件的数据属性流动到目标元素的属性。
People often describe property binding as one-way data binding because it flows a value in one direction,
from a component's data property into a target element property.
不能使用属性绑定来从目标元素拉取值,也不能绑定到目标元素的属性来读取它。只能设置它。
You cannot use property binding to pull values out of the target element.
You can't bind to a property of the target element to read it. You can only set it.
也不能使用属性 绑定 来调用目标元素上的方法。
Similarly, you cannot use property binding to call a method on the target element.
If you must read a target element property or call one of its methods,
you'll need a different technique.
See the API reference for
ViewChild and
ContentChild.
绑定目标
Binding target
包裹在方括号中的元素属性名标记着目标属性。下列代码中的目标属性是 image 元素的src属性。
An element property between enclosing square brackets identifies the target property. The target property in the following code is the image element's src property.
<img [src]="heroImageUrl">
有些人喜欢用bind-前缀的可选形式,并称之为规范形式:
Some people prefer the bind- prefix alternative, known as the canonical form:
The target name is always the name of a property, even when it appears to be the name of something else.
You see src and may think it's the name of an attribute. No. It's the name of an image element property.
Element properties may be the more common targets,
but Angular looks first to see if the name is a property of a known directive,
as it is in the following example:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
Technically, Angular is matching the name to a directive input,
one of the property names listed in the directive's inputs array or a property decorated with @Input().
Such inputs map to the directive's own properties.
如果名字没有匹配上已知指令或元素的属性,Angular 就会报告“未知指令”的错误。
If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error.
As mentioned previously, evaluation of a template expression should have no visible side effects.
The expression language itself does its part to keep you safe.
You can't assign a value to anything in a property binding expression nor use the increment and decrement operators.
当然,表达式可能会调用具有副作用的属性或方法。但 Angular 没法知道这一点,也没法阻止我们。
Of course, the expression might invoke a property or method that has side effects.
Angular has no way of knowing that or stopping you.
The expression could call something like getFoo(). Only you know what getFoo() does.
If getFoo() changes something and you happen to be binding to that something, you risk an unpleasant experience.
Angular may or may not display the changed value. Angular may detect the change and throw a warning error.
In general, stick to data properties and to methods that return values and do no more.
The template expression should evaluate to the type of value expected by the target property.
Return a string if the target property expects a string.
Return a number if the target property expects a number.
Return an object if the target property expects an object.
The brackets tell Angular to evaluate the template expression.
If you omit the brackets, Angular treats the string as a constant
and initializes the target property with that string.
It does not evaluate the string!
不要出现这样的失误:
Don't make the following mistake:
<!-- ERROR: HeroDetailComponent.hero expects a
Hero object, not the string "currentHero" --><hero-detailhero="currentHero"></hero-detail>
一次性字符串初始化
One-time string initialization
当满足下列条件时,应该省略括号:
You should omit the brackets when all of the following are true:
目标属性接受字符串值。
The target property accepts a string value.
字符串是个固定值,可以直接合并到模块中。
The string is a fixed value that you can bake into the template.
这个初始值永不改变。
This initial value never changes.
我们经常这样在标准 HTML 中用这种方式初始化 attribute,这种方式也可以用在初始化指令和组件的属性。
下面这个例子把HeroDetailComponent的prefix属性初始化为固定的字符串,而不是模板表达式。Angular 设置它,然后忘记它。
You routinely initialize attributes this way in standard HTML, and it works
just as well for directive and component property initialization.
The following example initializes the prefix property of the HeroDetailComponent to a fixed string,
not a template expression. Angular sets it and forgets about it.
<hero-detailprefix="You are my" [hero]="currentHero"></hero-detail>
作为对比,[hero]绑定是组件的currentHero属性的活绑定,它会一直随着更新。
The [hero] binding, on the other hand, remains a live binding to the component's currentHero property.
属性绑定还是插值表达式?
Property binding or interpolation?
我们通常得在插值表达式和属性绑定之间做出选择。
下列这几对绑定做的事情完全相同:
You often have a choice between interpolation and property binding.
The following binding pairs do the same thing:
<p><imgsrc="{{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>
When rendering data values as strings, there is no technical reason to prefer one form to the other.
You lean toward readability, which tends to favor interpolation.
You suggest establishing coding style rules and choosing the form that
both conforms to the rules and feels most natural for the task at hand.
但数据类型不是字符串时,就必须使用属性绑定了。
When setting an element property to a non-string data value, you must use property binding.
内容安全
Content security
假设下面的恶意内容
Imagine the following malicious content.
evilTitle ='Template <script>alert("evil never sleeps")</script>Syntax';
幸运的是,Angular 数据绑定对危险 HTML 有防备。
在显示它们之前,它对内容先进行消毒。
不管是插值表达式还是属性绑定,都不会允许带有 script 标签的 HTML 泄漏到浏览器中。
Fortunately, Angular data binding is on alert for dangerous HTML.
It sanitizes the values before displaying them.
It will not allow HTML with script tags to leak into the browser, neither with interpolation
nor property binding.
<!--
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>
插值表达式处理 script 标签与属性绑定有所不同,但是二者都只渲染没有危害的内容。
Interpolation handles the script tags differently than property binding but both approaches render the
content harmlessly.
This guide stresses repeatedly that setting an element property with a property binding
is always preferred to setting the attribute with a string. Why does Angular offer attribute binding?
因为当元素没有属性可绑的时候,就必须使用 attribute 绑定。
You must use attribute binding when there is no element property to bind.
Consider the ARIA,
SVG, and
table span attributes. They are pure attributes.
They do not correspond to element properties, and they do not set element properties.
There are no property targets to bind to.
如果想写出类似下面这样的东西,现状会令我们痛苦:
This fact becomes painfully obvious when you write something like this.
<tr><tdcolspan="{{1 + 1}}">Three-Four</td></tr>
会得到这个错误:
And you get this error:
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property
(模板解析错误:不能绑定到 'colspan',因为它不是已知的原生属性)
As the message says, the <td> element does not have a colspan property.
It has the "colspan" attribute, but
interpolation and property binding can set only properties, not attributes.
我们需要 attribute 绑定来创建和绑定到这样的 attribute。
You need attribute bindings to create and bind to such attributes.
Attribute binding syntax resembles property binding.
Instead of an element property between brackets, start with the prefix attr,
followed by a dot (.) and the name of the attribute.
You then set the attribute value, using an expression that resolves to a string.
这里把[attr.colspan]绑定到一个计算值:
Bind [attr.colspan] to a calculated value:
<tableborder=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>
Class binding syntax resembles property binding.
Instead of an element property between brackets, start with the prefix class,
optionally followed by a dot (.) and the name of a CSS class: [class.class-name].
The following examples show how to add and remove the application's "special" class
with class bindings. Here's how to set the attribute without binding:
<!-- standard class attribute setting --><divclass="bad curly special">Bad curly special</div>
可以把它改写为绑定到所需 CSS 类名的绑定;这是一个或者全有或者全无的替换型绑定。
(译注:即当 badCurly 有值时 class 这个 attribute 设置的内容会被完全覆盖)
You can replace that with a binding to a string of the desired class names; this is an all-or-nothing, replacement binding.
<!-- reset/override all class names with a binding --><divclass="bad curly special"
[class]="badCurly">Bad curly</div>
Finally, you can bind to a specific class name.
Angular adds the class when the template expression evaluates to truthy.
It removes the class when the expression is falsy.
<!-- 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 --><divclass="special"
[class.special]="!isSpecial">This one is not so special</div>
While this is a fine way to toggle a single class name,
the NgClass directive is usually preferred when managing multiple class names at the same time.
Style binding syntax resembles property binding.
Instead of an element property between brackets, start with the prefix style,
followed by a dot (.) and the name of a CSS style property: [style.style-property].
Users don't just stare at the screen. They enter text into input boxes. They pick items from lists.
They click buttons. Such user actions may result in a flow of data in the opposite direction:
from an element to a component.
The only way to know about a user action is to listen for certain events such as
keystrokes, mouse movements, clicks, and touches.
You declare your interest in user actions through Angular event binding.
Event binding syntax consists of a target event name
within parentheses on the left of an equal sign, and a quoted
template statement on the right.
The following event binding listens for the button's click events, calling
the component's onSave() method whenever a click occurs:
Element events may be the more common targets, but Angular looks first to see if the name matches an event property
of a known directive, as it does in the following example:
<!-- `myClick` is an event on the custom `ClickDirective` --><div (myClick)="clickMessage=$event"clickable>click with myClick</div>
If the name fails to match an element event or an output property of a known directive,
Angular reports an “unknown directive” error.
$event 和事件处理语句
$event and event handling statements
在事件绑定中,Angular 会为目标事件设置事件处理器。
In an event binding, Angular sets up an event handler for the target event.
当事件发生时,这个处理器会执行模板语句。
典型的模板语句通常涉及到响应事件执行动作的接收器,例如从 HTML 控件中取得值,并存入模型。
When the event is raised, the handler executes the template statement.
The template statement typically involves a receiver, which performs an action
in response to the event, such as storing a value from the HTML control
into a model.
绑定会通过名叫$event的事件对象传递关于此事件的信息(包括数据值)。
The binding conveys information about the event, including data values, through
an event object named $event.
事件对象的形态取决于目标事件。如果目标事件是原生 DOM 元素事件,
$event就是 DOM事件对象,它有像target和target.value这样的属性。
The shape of the event object is determined by the target event.
If the target event is a native DOM element event, then $event is a
DOM event object,
with properties such as target and target.value.
上面的代码在把输入框的value属性绑定到firstName属性。
要监听对值的修改,代码绑定到输入框的input事件。
当用户造成更改时,input事件被触发,并在包含了 DOM 事件对象 ($event) 的上下文中执行这条语句。
This code sets the input box value property by binding to the name property.
To listen for changes to the value, the code binds to the input box's input event.
When the user makes changes, the input event is raised, and the binding executes
the statement within a context that includes the DOM event object, $event.
要更新firstName属性,就要通过路径$event.target.value来获取更改后的值。
To update the name property, the changed text is retrieved by following the path $event.target.value.
如果事件属于指令(回想一下,组件是指令的一种),那么$event具体是什么由指令决定。
If the event belongs to a directive (recall that components are directives),
$event has whatever shape the directive decides to produce.
Directives typically raise custom events with an Angular EventEmitter.
The directive creates an EventEmitter and exposes it as a property.
The directive calls EventEmitter.emit(payload) to fire an event, passing in a message payload, which can be anything.
Parent directives listen for the event by binding to this property and accessing the payload through the $event object.
Consider a HeroDetailComponent that presents hero information and responds to user actions.
Although the HeroDetailComponent has a delete button it doesn't know how to delete the hero itself.
The best it can do is raise an event reporting the user's delete request.
下面的代码节选自HeroDetailComponent:
Here are the pertinent excerpts from that HeroDetailComponent:
// This component make a request but it can't actually delete a hero.
deleteRequest =newEventEmitter<Hero>();delete(){this.deleteRequest.emit(this.hero);}
The component defines a deleteRequest property that returns an EventEmitter.
When the user clicks delete, the component invokes the delete() method,
telling the EventEmitter to emit a Hero object.
When the deleteRequest event fires, Angular calls the parent component's deleteHero method,
passing the hero-to-delete (emitted by HeroDetail) in the $event variable.
Deleting the hero updates the model, perhaps triggering other changes
including queries and saves to a remote server.
These changes percolate through the system and are ultimately displayed in this and other views.
Angular offers a special two-way data binding syntax for this purpose, [(x)].
The [(x)] syntax combines the brackets
of property binding, [x], with the parentheses of event binding, (x).
[( )] = 盒子里的香蕉[( )] = banana in a box
想象盒子里的香蕉来记住方括号套圆括号。
Visualize a banana in a box to remember that the parentheses go inside the brackets.
The [(x)] syntax is easy to demonstrate when the element has a settable property called x
and a corresponding event named xChange.
Here's a SizerComponent that fits the pattern.
It has a size value property and a companion sizeChange event:
The initial size is an input value from a property binding.
Clicking the buttons increases or decreases the size, within min/max values constraints,
and then raises (emits) the sizeChange event with the adjusted size.
下面的例子中,AppComponent.fontSize被双向绑定到SizerComponent:
Here's an example in which the AppComponent.fontSizePx is two-way bound to the SizerComponent:
The AppComponent.fontSizePx establishes the initial SizerComponent.size value.
Clicking the buttons updates the AppComponent.fontSizePx via the two-way binding.
The revised AppComponent.fontSizePx value flows through to the style binding,
making the displayed text bigger or smaller.
The two-way binding syntax is really just syntactic sugar for a property binding and an event binding.
Angular desugars the SizerComponent binding into this:
The $event variable contains the payload of the SizerComponent.sizeChange event.
Angular assigns the $event value to the AppComponent.fontSizePx when the user clicks the buttons.
显然,比起单独绑定属性和事件,双向数据绑定语法显得非常方便。
Clearly the two-way binding syntax is a great convenience compared to separate property and event bindings.
我们希望能在像<input>和<select>这样的 HTML 元素上使用双向数据绑定。
可惜,原生 HTML 元素不遵循x值和xChange事件的模式。
It would be convenient to use two-way binding with HTML form elements like <input> and <select>.
However, no native HTML element follows the x value and xChange event pattern.
Earlier versions of Angular included over seventy built-in directives.
The community contributed many more, and countless private directives
have been created for internal applications.
You don't need many of those directives in Angular.
You can often achieve the same results with the more capable and expressive Angular binding system.
Why create a directive to handle a click when you can write a simple binding such as this?
You still benefit from directives that simplify complex tasks.
Angular still ships with built-in directives; just not as many.
You'll write your own directives, just not as many.
Attribute directives listen to and modify the behavior of
other HTML elements, attributes, properties, and components.
They are usually applied to elements as if they were HTML attributes, hence the name.
Many details are covered in the Attribute Directives guide.
Many Angular modules such as the RouterModule
and the FormsModule define their own attribute directives.
This section is an introduction to the most commonly used attribute directives:
You typically control how elements appear
by adding and removing CSS classes dynamically.
You can bind to the ngClass to add or remove several classes simultaneously.
A class binding is a good way to add or remove a single class.
<!-- toggle the "special" class on/off with a property --><div [class.special]="isSpecial">The class binding is special</div>
当想要同时添加或移除多个 CSS 类时,NgClass指令可能是更好的选择。
To add or remove many CSS classes at the same time, the NgClass directive may be the better choice.
试试把ngClass绑定到一个 key:value 形式的控制对象。这个对象中的每个 key 都是一个 CSS 类名,如果它的 value 是true,这个类就会被加上,否则就会被移除。
Try binding ngClass to a key:value control object.
Each key of the object is a CSS class name; its value is true if the class should be added,
false if it should be removed.
Consider a setCurrentClasses component method that sets a component property,
currentClasses with an object that adds or removes three classes based on the
true/false state of three other component properties:
currentClasses:{};
setCurrentClasses(){// CSS classes: added/removed per current state of component propertiesthis.currentClasses ={
saveable:this.canSave,
modified:!this.isUnchanged,
special:this.isSpecial
};}
把NgClass属性绑定到currentClasses,根据它来设置此元素的CSS类:
Adding an ngClass property binding to currentClasses sets the element's classes accordingly:
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
你既可以在初始化时调用setCurrentClassess(),也可以在所依赖的属性变化时调用。
It's up to you to call setCurrentClassess(), both initially and when the dependent properties change.
Consider a setCurrentStyles component method that sets a component property, currentStyles
with an object that defines three styles, based on the state of three other component propertes:
currentStyles:{};
setCurrentStyles(){// CSS styles: set per current state of component propertiesthis.currentStyles ={'font-style':this.canSave ?'italic':'normal','font-weight':!this.isUnchanged ?'bold':'normal','font-size':this.isSpecial ?'24px':'12px'};}
把NgStyle属性绑定到currentStyles,以据此设置此元素的样式:
Adding an ngStyle property binding to currentStyles sets the element's styles accordingly:
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
你既可以在初始化时调用setCurrentStyles(),也可以在所依赖的属性变化时调用。
It's up to you to call setCurrentStyles(), both initially and when the dependent properties change.
Before using the ngModel directive in a two-way data binding,
you must import the FormsModule and add it to the Angular module's imports list.
Learn more about the FormsModule and ngModel in the
Forms guide.
导入FormsModule并让[(ngModel)]可用的代码如下:
Here's how to import the FormsModule to make [(ngModel)] available.
src/app/app.module.ts (FormsModule import)
import{NgModule}from'@angular/core';import{BrowserModule}from'@angular/platform-browser';import{FormsModule}from'@angular/forms';// <--- JavaScript import from Angular/* Other imports */@NgModule({
imports:[BrowserModule,FormsModule// <--- import into the NgModule],/* Other module metadata */})exportclassAppModule{}
Looking back at the name binding, note that
you could have achieved the same result with separate bindings to
the <input> element's value property and input event.
That's cumbersome. Who can remember which element property to set and which element event emits user changes?
How do you extract the currently displayed text from the input box so you can update the data property?
Who wants to look that up each time?
The details are specific to each kind of element and therefore the NgModel directive only works for an element
supported by a ControlValueAccessor
that adapts an element to this protocol.
The <input> box is one of those elements.
Angular provides value accessors for all of the basic HTML form elements and the
Forms guide shows how to bind to them.
You can't apply [(ngModel)] to a non-form native element or a third-party custom component
until you write a suitable value accessor,
a technique that is beyond the scope of this guide.
You don't need a value accessor for an Angular component that you write because you
can name the value and event properties
to suit Angular's basic two-way binding syntax and skip NgModel altogether.
The sizer shown above is an example of this technique.
使用独立的ngModel绑定优于绑定到该元素的原生属性,那样我们可以做得更好。
Separate ngModel bindings is an improvement over binding to the element's native properties. You can do better.
You shouldn't have to mention the data property twice. Angular should be able to capture
the component's data property and set it
with a single declaration, which it can with the [(ngModel)] syntax:
<input [(ngModel)]="currentHero.name">
[(ngModel)]就是你需要的一切吗?有没有什么理由回退到它的展开形式?
Is [(ngModel)] all you need? Is there ever a reason to fall back to its expanded form?
Structural directives are responsible for HTML layout.
They shape or reshape the DOM's structure, typically by adding, removing, and manipulating
the host elements to which they are attached.
You can add or remove an element from the DOM by applying an NgIf directive to
that element (called the host elment).
Bind the directive to a condition expression like isActive in this example.
When the isActive expression returns a truthy value, NgIf adds the HeroDetailComponent to the DOM.
When the expression is falsy, NgIf removes the HeroDetailComponent
from the DOM, destroying that component and all of its sub-components.
You can control the visibility of an element with a
class or style binding:
<!-- 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>
但隐藏子树和用NgIf排除子树是截然不同的。
Hiding an element is quite different from removing an element with NgIf.
当隐藏子树时,它仍然留在 DOM 中。
子树中的组件及其状态仍然保留着。
即使对于不可见属性,Angular 也会继续检查变更。
子树可能占用相当可观的内存和运算资源。
When you hide an element, that element and all of its descendents remain in the DOM.
All components for those elements stay in memory and
Angular may continue to check for changes.
You could be holding onto considerable computing resources and degrading performance,
for something the user can't see.
当NgIf为false时,Angular 从 DOM 中物理地移除了这个元素子树。
它销毁了子树中的组件及其状态,也潜在释放了可观的资源,最终让用户体验到更好的性能。
When NgIf is false, Angular removes the element and its descendents from the DOM.
It destroys their components, potentially freeing up substantial resources,
resulting in a more responsive user experience.
The show/hide technique is fine for a few elements with few children.
You should be wary when hiding large component trees; NgIf may be the safer choice.
The ngIf directive is often used to guard against null.
Show/hide is useless as a guard.
Angular will throw an error if a nested expression tries to access a property of null.
NgFor是一个重复器指令 —— 自定义数据显示的一种方式。
我们的目标是展示一个由多个条目组成的列表。首先定义了一个 HTML 块,它规定了单个条目应该如何显示。
再告诉 Angular 把这个块当做模板,渲染列表中的每个条目。
NgFor is a repeater directive — a way to present a list of items.
You define a block of HTML that defines how a single item should be displayed.
You tell Angular to use that block as a template for rendering each item in the list.
下例中,NgFor应用在一个简单的<div>上:
Here is an example of NgFor applied to a simple <div>:
<div *ngFor="let hero of heroes">{{hero.name}}</div>
也可以把NgFor应用在一个组件元素上,就下例这样:
You can also apply an NgFor to a component element, as in this example:
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
不要忘了ngFor前面的星号 (*)。
Don't forget the asterisk (*) in front of ngFor.
赋值给*ngFor的文本是用于指导重复器如何工作的指令。
The text assigned to *ngFor is the instruction that guides the repeater process.
NgFor 微语法
*ngFor microsyntax
赋值给*ngFor的字符串不是模板表达式。
它是一个微语法 —— 由 Angular 自己解释的小型语言。在这个例子中,字符串"let hero of heroes"的含义是:
The string assigned to *ngFor is not a template expression.
It's a microsyntax — a little language of its own that Angular interprets.
The string "let hero of heroes" means:
取出heroes数组中的每个英雄,把它存入局部变量hero中,并在每次迭代时对模板 HTML 可用
Take each hero in the heroes array, store it in the local hero looping variable, and
make it available to the templated HTML for each iteration.
Angular translates this instruction into a <ng-template> around the host element,
then uses this template repeatedly to create a new set of elements and bindings for each hero
in the list.
The let keyword before hero creates a template input variable called hero.
The ngFor directive iterates over the heroes array returned by the parent component's heroes property
and sets hero to the current item from the array during each iteration.
You reference the hero input variable within the ngFor host element
(and within its descendents) to access the hero's properties.
Here it is referenced first in an interpolation
and then passed in a binding to the hero property of the <hero-detail> component.
<div *ngFor="let hero of heroes">{{hero.name}}</div><hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
The index property of the NgFor directive context returns the zero-based index of the item in each iteration.
You can capture the index in a template input variable and use it in the template.
下面这个例子把index捕获到了i变量中,并且把它显示在英雄名字的前面。
The next example captures the index in a variable named i and displays it with the hero name like this.
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
要学习更多的类似 index 的值,例如last、even和odd,请参阅 NgFor API 参考。
Learn about the other NgFor context values such as last, even,
and odd in the NgFor API reference.
带trackBy的*ngFor
*ngFor with trackBy
ngFor指令有时候会性能较差,特别是在大型列表中。
对一个条目的一丁点改动、移除或添加,都会导致级联的 DOM 操作。
The NgFor directive may perform poorly, especially with large lists.
A small change to one item, an item removed, or an item added can trigger a cascade of DOM manipulations.
For example, re-querying the server could reset the list with all new hero objects.
它们中的绝大多数(如果不是所有的话)都是以前显示过的英雄。我们知道这一点,是因为每个英雄的id没有变化。
但在 Angular 看来,它只是一个由新的对象引用构成的新列表,
它没有选择,只能清理旧列表、舍弃那些 DOM 元素,并且用新的 DOM 元素来重建一个新列表。
Most, if not all, are previously displayed heroes.
You know this because the id of each hero hasn't changed.
But Angular sees only a fresh list of new object references.
It has no choice but to tear down the old DOM elements and insert all new DOM elements.
Angular can avoid this churn with trackBy.
Add a method to the component that returns the value NgForshould track.
In this case, that value is the hero's id.
trackByHeroes(index: number, hero:Hero): number {return hero.id;}
在微语法中,把trackBy设置为该方法。
In the microsyntax expression, set trackBy to this method.
<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
({{hero.id}}) {{hero.name}}
</div>
Here is an illustration of the trackBy effect.
"Reset heroes" creates new heroes with the same hero.ids.
"Change ids" creates new heroes with new hero.ids.
如果没有trackBy,这些按钮都会触发完全的DOM元素替换。
With no trackBy, both buttons trigger complete DOM element replacement.
有了trackBy,则只有修改了id的按钮才会触发元素替换。
With trackBy, only changing the id triggers element replacement.
NgSwitch is like the JavaScript switch statement.
It can display one element from among several possible elements, based on a switch condition.
Angular puts only the selected element into the DOM.
NgSwitch is the controller directive. Bind it to an expression that returns the switch value.
The emotion value in this example is a string, but the switch value can be of any type.
Bind to [ngSwitch]. You'll get an error if you try to set *ngSwitch because
NgSwitch is an attribute directive, not a structural directive.
It changes the behavior of its companion directives.
It doesn't touch the DOM directly.
Bind to *ngSwitchCase and *ngSwitchDefault.
The NgSwitchCase and NgSwitchDefault directives are structural directives
because they add or remove elements from the DOM.
NgSwitchCase会在它绑定到的值等于候选值时,把它所在的元素加入到DOM中。
NgSwitchCase adds its element to the DOM when its bound value equals the switch value.
The switch directives are particularly useful for adding and removing component elements.
This example switches among four "emotional hero" components defined in the hero-switch.components.ts file.
Each component has a heroinput property
which is bound to the currentHero of the parent component.
Switch directives work as well with native elements and web components too.
For example, you could replace the <confused-hero> switch case with the following.
<div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
A template reference variable is often a reference to a DOM element within a template.
It can also be a reference to an Angular component or directive or a
web component.
You can refer to a template reference variable anywhere in the template.
The phone variable declared on this <input> is
consumed in a <button> on the other side of the template
<input #phoneplaceholder="phone number"><!-- lots of other elements --><!-- phone refers to the input element; pass its `value` to an event handler --><button (click)="callPhone(phone.value)">Call</button>
In most cases, Angular sets the reference variable's value to the element on which it was declared.
In the previous example, phone refers to the phone number<input> box.
The phone button click handler passes the input value to the component's callPhone method.
But a directive can change that behavior and set the value to something else, such as itself.
The NgForm directive does that.
If Angular hadn't taken it over when you imported the FormsModule,
it would be the HTMLFormElement.
The heroForm is actually a reference to an Angular NgForm
directive with the ability to track the value and validity of every control in the form.
The native <form> element doesn't have a form property.
But the NgForm directive does, which explains how you can disable the submit button
if the heroForm.form.valid is invalid and pass the entire form control tree
to the parent component's onSubmit method.
A template reference variable (#phone) is not the same as a template input variable (let phone)
such as you might see in an *ngFor.
Learn the difference in the Structural Directives guide.
The scope of a reference variable is the entire template.
Do not define the same variable name more than once in the same template.
The runtime value will be unpredictable.
So far, you've focused mainly on binding to component members within template expressions and statements
that appear on the right side of the binding declaration.
A member in that position is a data binding source.
本节则专注于绑定到的目标,它位于绑定声明中的左侧。
这些指令的属性必须被声明成输入或输出。
This section concentrates on binding to targets, which are directive
properties on the left side of the binding declaration.
These directive properties must be declared as inputs or outputs.
记住:所有组件皆为指令。
Remember: All components are directives.
我们要重点突出下绑定目标和绑定源的区别。
Note the important distinction between a data binding target and a data binding source.
绑定的目标是在=左侧的部分,
源则是在=右侧的部分。
The target of a binding is to the left of the =.
The source is on the right of the =.
The target of a binding is the property or event inside the binding punctuation: [], () or [()].
The source is either inside quotes (" ") or within an interpolation ({{}}).
Every member of a source directive is automatically available for binding.
You don't have to do anything special to access a directive member in a template expression or statement.
访问目标指令中的成员则受到限制。
只能绑定到那些显式标记为输入或输出的属性。
You have limited access to members of a target directive.
You can only bind to properties that are explicitly identified as inputs and outputs.
在下面的例子中,iconUrl和onSave是组件的成员,它们在=右侧引号语法中被引用了。
In the following snippet, iconUrl and onSave are data-bound members of the AppComponent
and are referenced within quoted syntax to the right of the equals (=).
Both HeroDetailComponent.hero and HeroDetailComponent.deleteRequest are on the left side of binding declarations.
HeroDetailComponent.hero is inside brackets; it is the target of a property binding.
HeroDetailComponent.deleteRequest is inside parentheses; it is the target of an event binding.
声明输入和输出属性
Declaring input and output properties
目标属性必须被显式的标记为输入或输出。
Target properties must be explicitly marked as inputs or outputs.
在HeroDetailComponent内部,这些属性被装饰器标记成了输入和输出属性。
In the HeroDetailComponent, such properties are marked as input or output properties using decorators.
HeroDetailComponent.hero is an input property from the perspective of HeroDetailComponent
because data flows into that property from a template binding expression.
HeroDetailComponent.deleteRequest is an output property from the perspective of HeroDetailComponent
because events stream out of that property and toward the handler in a template binding statement.
给输入/输出属性起别名
Aliasing input/output properties
有时需要让输入/输出属性的公开名字不同于内部名字。
Sometimes the public name of an input/output property should be different from the internal name.
This is frequently the case with attribute directives.
Directive consumers expect to bind to the name of the directive.
For example, when you apply a directive with a myClick selector to a <div> tag,
you expect to bind to an event property that is also called myClick.
<div (myClick)="clickMessage=$event"clickable>click with myClick</div>
However, the directive name is often a poor choice for the name of a property within the directive class.
The directive name rarely describes what the property does.
The myClick directive name is not a good name for a property that emits click messages.
Fortunately, you can have a public name for the property that meets conventional expectations,
while using a different name internally.
In the example immediately above, you are actually binding through themyClickalias to
the directive's own clicks property.
把别名传进@Input/@Output装饰器,就可以为属性指定别名,就像这样:
You can specify the alias for the property name by passing it into the input/output decorator like this:
You can also alias property names in the inputs and outputs arrays.
You write a colon-delimited (:) string with
the directive property name on the left and the public alias on the right:
The template expression language employs a subset of JavaScript syntax supplemented with a few special operators
for specific scenarios. The next sections cover two of these operators: pipe and safe navigation operator.
The result of an expression might require some transformation before you're ready to use it in a binding.
For example, you might display a number as a currency, force text to uppercase, or filter a list and sort it.
Angular pipes are a good choice for small transformations such as these.
Pipes are simple functions that accept an input value and return a transformed value.
They're easy to apply within template expressions, using the pipe operator (|):
<div>Title through uppercase pipe: {{title | uppercase}}</div>
管道操作符会把它左侧的表达式结果传给它右侧的管道函数。
The pipe operator passes the result of an expression on the left to a pipe function on the right.
还可以通过多个管道串联表达式:
You can chain expressions through multiple pipes:
<!-- Pipe chaining: convert title to uppercase, then to lowercase --><div>
Title through a pipe chain:
{{title | uppercase | lowercase}}
</div>
The Angular safe navigation operator (?.) is a fluent and convenient way to
guard against null and undefined values in property paths.
Here it is, protecting against a view render failure if the currentHero is null.
The current hero's name is {{currentHero?.name}}
如果下列数据绑定中title属性为空,会发生什么?
What happens when the following data bound title property is null?
The title is {{title}}
这个视图仍然被渲染出来,但是显示的值是空;只能看到 “The title is”,它后面却没有任何东西。
这是合理的行为。至少应用没有崩溃。
The view still renders but the displayed value is blank; you see only "The title is" with nothing after it.
That is reasonable behavior. At least the app doesn't crash.
假设模板表达式涉及属性路径,在下例中,显示一个空 (null) 英雄的firstName。
Suppose the template expression involves a property path, as in this next example
that displays the name of a null hero.
The null hero's name is {{nullHero.name}}
JavaScript 抛出了空引用错误,Angular 也是如此:
JavaScript throws a null reference error, and so does Angular:
TypeError: Cannot read property 'name' of null in [null].
This would be reasonable behavior if the hero property could never be null.
If it must never be null and yet it is null,
that's a programming error that should be caught and fixed.
Throwing an exception is the right thing to do.
另一方面,属性路径中的空值可能会时常发生,特别是当我们知道数据最终会出现。
On the other hand, null values in the property path may be OK from time to time,
especially when the data are null now and will arrive eventually.
These approaches have merit but can be cumbersome, especially if the property path is long.
Imagine guarding against a null somewhere in a long property path such as a.b.c.d.
The Angular safe navigation operator (?.) is a more fluent and convenient way to guard against nulls in property paths.
The expression bails out when it hits the first null value.
The display is blank, but the app keeps rolling without errors.
<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.name}}
在像a?.b?.c?.d这样的长属性路径中,它工作得很完美。
It works perfectly with long property paths such as a?.b?.c?.d.