docs: edit property binding doc (#38799)

This commit edits the property binding doc copy and adds some
docregions to clarify explanations.

PR Close #38799
This commit is contained in:
Kapunahele Wong 2020-09-10 17:06:39 -04:00 committed by Andrew Kushnir
parent cfac8e0764
commit 3e6f24edcd
6 changed files with 186 additions and 161 deletions

View File

@ -380,6 +380,7 @@ groups:
'aio/content/examples/binding-syntax/**', 'aio/content/examples/binding-syntax/**',
'aio/content/guide/property-binding.md', 'aio/content/guide/property-binding.md',
'aio/content/examples/property-binding/**', 'aio/content/examples/property-binding/**',
'aio/content/guide/property-binding-best-practices.md',
'aio/content/guide/attribute-binding.md', 'aio/content/guide/attribute-binding.md',
'aio/content/examples/attribute-binding/**', 'aio/content/examples/attribute-binding/**',
'aio/content/guide/two-way-binding.md', 'aio/content/guide/two-way-binding.md',

View File

@ -7,9 +7,15 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent { export class AppComponent {
// #docregion item-image
itemImageUrl = '../assets/phone.png'; itemImageUrl = '../assets/phone.png';
// #enddocregion item-image
// #docregion boolean
isUnchanged = true; isUnchanged = true;
// #enddocregion boolean
// #docregion directive-property
classes = 'special'; classes = 'special';
// #enddocregion directive-property
// #docregion parent-data-type // #docregion parent-data-type
parentItem = 'lamp'; parentItem = 'lamp';
// #enddocregion parent-data-type // #enddocregion parent-data-type

View File

@ -99,7 +99,7 @@ in the `$event` variable.
## Template statements have side effects ## Template statements have side effects
Though [template expressions](guide/interpolation#template-expressions) shouldn't have [side effects](guide/property-binding#avoid-side-effects), template Though [template expressions](guide/interpolation#template-expressions) shouldn't have [side effects](guide/property-binding-best-practices#avoid-side-effects), template
statements usually do. The `deleteItem()` method does have statements usually do. The `deleteItem()` method does have
a side effect: it deletes an item. a side effect: it deletes an item.

View File

@ -0,0 +1,65 @@
# Property binding best practices
By following a few guidelines, you can use property binding in a way that helps you minimize bugs and keep your code readable.
<div class="alert is-helpful">
See the <live-example name="property-binding"></live-example> for a working example containing the code snippets in this guide.
</div>
## Avoid side effects
Evaluation of a template expression should have no visible side effects.
Use the syntax for template expressions to help avoid side effects.
In general, the correct syntax prevents you from assigning a value to anything in a property binding expression.
The syntax also prevents you from using increment and decrement operators.
### An example of producing side effects
If you had an expression that changed the value of something else that you were binding to, that change of value would be a side effect.
Angular might or might not display the changed value.
If Angular does detect the change, it throws an error.
As a best practice, use only properties and methods that return values.
## Return the proper type
A template expression should evaluate to the type of value that the target property expects.
For example, return a string if the target property expects a string, a number if it expects a number, or an object if it expects an object.
### Passing in a string
In the following example, the `childItem` property of the `ItemDetailComponent` expects a string.
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example>
You can confirm this expectation by looking in the `ItemDetailComponent` where the `@Input()` type is `string`:
<code-example path="property-binding/src/app/item-detail/item-detail.component.ts" region="input-type" header="src/app/item-detail/item-detail.component.ts (setting the @Input() type)"></code-example>
The `parentItem` in `AppComponent` is a string, which means that the expression, `parentItem` within `[childItem]="parentItem"`, evaluates to a string.
<code-example path="property-binding/src/app/app.component.ts" region="parent-data-type" header="src/app/app.component.ts"></code-example>
If `parentItem` were some other type, you would need to specify `childItem` `@Input()` as that type as well.
### Passing in an object
In this example, `ItemListComponent` is a child component of `AppComponent` and the `items` property expects an array of objects.
<code-example path="property-binding/src/app/app.component.html" region="pass-object" header="src/app/app.component.html"></code-example>
In the `ItemListComponent` the `@Input()`, `items`, has a type of `Item[]`.
<code-example path="property-binding/src/app/item-list/item-list.component.ts" region="item-input" header="src/app/item-list.component.ts"></code-example>
Notice that `Item` is an object that it has two properties; an `id` and a `name`.
<code-example path="property-binding/src/app/item.ts" region="item-class" header="src/app/item.ts"></code-example>
In `app.component.ts`, `currentItems` is an array of objects in the same shape as the `Item` object in `items.ts`, with an `id` and a `name`.
<code-example path="property-binding/src/app/app.component.ts" region="pass-object" header="src/app.component.ts"></code-example>
By supplying an object in the same shape, you satisfy the expectations of `items` when Angular evaluates the expression `currentItems`.

View File

@ -1,8 +1,8 @@
# Property binding `[property]` # Property binding
Use property binding to _set_ properties of target elements or Property binding in Angular helps you set values for properties of HTML elements or directives.
directive `@Input()` decorators. With property binding, you can do things such as toggle button functionality, set paths programatically, and share values between components.
<div class="alert is-helpful"> <div class="alert is-helpful">
@ -10,34 +10,57 @@ See the <live-example></live-example> for a working example containing the code
</div> </div>
## One-way in ## Prerequisites
Property binding flows a value in one direction, To get the most out of property binding, you should be familiar with the following:
from a component's property into a target element property.
You can't use property * [Basics of components](guide/architecture-components)
binding to read or pull values out of target elements. Similarly, you cannot use * [Basics of templates](guide/glossary#template)
property binding to call a method on the target element. * [Binding syntax](guide/binding-syntax)
If the element raises events, you can listen to them with an [event binding](guide/event-binding).
If you must read a target element property or call one of its methods, <hr />
see the API reference for [ViewChild](api/core/ViewChild) and
[ContentChild](api/core/ContentChild).
## Examples ## Understanding the flow of data
The most common property binding sets an element property to a component Property binding moves a value in one direction, from a component's property into a target element property.
property value. An example is
binding the `src` property of an image element to a component's `itemImageUrl` property: <div class="alert is-helpful">
For more information on listening for events, see [Event binding](guide/event-binding).
</div>
To read a target element property or call one of its methods, see the API reference for [ViewChild](api/core/ViewChild) and [ContentChild](api/core/ContentChild).
## Binding to a property
To bind to an element's property, enclose it in square brackets, `[]`, which identifies the property as a target property.
A target property is the DOM property to which you want to assign a value.
For example, the target property in the following code is the image element's `src` property.
<code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html"></code-example> <code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html"></code-example>
Here's an example of binding to the `colSpan` property. Notice that it's not `colspan`,
which is the attribute, spelled with a lowercase `s`.
<code-example path="property-binding/src/app/app.component.html" region="colSpan" header="src/app/app.component.html"></code-example> In most cases, the target name is the name of a property, even when it appears to be the name of an attribute.
In this example, `src` is the name of the `<img>` element property.
For more details, see the [MDN HTMLTableCellElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) documentation. The brackets, `[]`, cause Angular to evaluate the right-hand side of the assignment as a dynamic expression.
Without the brackets, Angular treats the the right-hand side as a string literal and sets the property to that static value.
<code-example path="property-binding/src/app/app.component.html" region="no-evaluation" header="src/app.component.html"></code-example>
Omitting the brackets renders the string `parentItem`, not the value of `parentItem`.
## Setting an element property to a component property value
To bind the `src` property of an `<img>` element to a component's property, place the target, `src`, in square brackets followed by an equal sign and then the property.
The property here is `itemImageUrl`.
<code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html"></code-example>
Declare the `itemImageUrl` property in the class, in this case `AppComponent`.
<code-example path="property-binding/src/app/app.component.ts" region="item-image" header="src/app/app.component.ts"></code-example>
{@a colspan} {@a colspan}
@ -81,175 +104,100 @@ for parent and child components to communicate:
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example> <code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example>
## Binding targets
An element property between enclosing square brackets identifies the target property. ## Toggling button functionality
The target property in the following code is the image element's `src` property.
<code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html"></code-example> To disable a button's functionality depending on a Boolean value, bind the DOM `disabled` property to a property in the class that is `true` or `false`.
There's also the `bind-` prefix alternative: <code-example path="property-binding/src/app/app.component.html" region="disabled-button" header="src/app/app.component.html"></code-example>
<code-example path="property-binding/src/app/app.component.html" region="bind-prefix" header="src/app/app.component.html"></code-example> Because the value of the property `isUnchanged` is `true` in the `AppComponent`, Angular disables the button.
<code-example path="property-binding/src/app/app.component.ts" region="boolean" header="src/app/app.component.ts"></code-example>
In most cases, the target name is the name of a property, even ## Setting a directive property
when it appears to be the name of an attribute.
So in this case, `src` is the name of the `<img>` element property.
Element properties may be the more common targets, To set a property of a directive, place the directive within square brackets , such as `[ngClass]`, followed by an equal sign and the property.
but Angular looks first to see if the name is a property of a known directive, Here, the property is `classes`.
as it is in the following example:
<code-example path="property-binding/src/app/app.component.html" region="class-binding" header="src/app/app.component.html"></code-example> <code-example path="property-binding/src/app/app.component.html" region="class-binding" header="src/app/app.component.html"></code-example>
Technically, Angular is matching the name to a directive `@Input()`, To use the property, you must declare it in the class, which in this example is `AppComponent`.
one of the property names listed in the directive's `inputs` array The value of `classes` is `special`.
or a property decorated with `@Input()`.
Such inputs map to the directive's own properties.
If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error. <code-example path="property-binding/src/app/app.component.ts" region="directive-property" header="src/app/app.component.ts"></code-example>
<div class="alert is-helpful"> Angular applies the class `special` to the `<p>` element so that you can use `special` to apply CSS styles.
Though the target name is usually the name of a property, ## Bind values between components
there is an automatic attribute-to-property mapping in Angular for
several common attributes. These include `class`/`className`, `innerHtml`/`innerHTML`, and
`tabindex`/`tabIndex`.
</div> To set the model property of a custom component, place the target, here `childItem`, between square brackets `[]` followed by an equal sign and the property.
Here, the property is `parentItem`.
## Avoid side effects
Evaluation of a template expression should have no visible side effects.
The expression language itself, or the way you write template expressions,
helps to a certain extent;
you can't assign a value to anything in a property binding expression
nor use the increment and decrement operators.
For example, you could have an expression that invoked a property or method that had
side effects. The expression could call something like `getFoo()` where only you
know what `getFoo()` does. If `getFoo()` changes something
and you happen to be binding to that something,
Angular may or may not display the changed value. Angular may detect the
change and throw a warning error.
As a best practice, stick to properties and to methods that return
values and avoid side effects.
## Return the proper type
The template expression should evaluate to the type of value
that the target property expects.
Return a string if the target property expects a string, a number if it
expects a number, an object if it expects an object, and so on.
In the following example, the `childItem` property of the `ItemDetailComponent` expects a string, which is exactly what you're sending in the property binding:
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example> <code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example>
You can confirm this by looking in the `ItemDetailComponent` where the `@Input` type is set to a string: To use the target and the property, you must declare them in their respective classes.
<code-example path="property-binding/src/app/item-detail/item-detail.component.ts" region="input-type" header="src/app/item-detail/item-detail.component.ts (setting the @Input() type)"></code-example>
Declare the target of `childItem` in its component class, in this case `ItemDetailComponent`.
For example, the following code declares the target of `childItem` in its component class, in this case `ItemDetailComponent`.
Then, the code contains an `@Input()` decorator with the `childItem` property so data can flow into it.
<code-example path="property-binding/src/app/item-detail/item-detail.component.ts" region="input-type" header="src/app/item-detail/item-detail.component.ts"></code-example>
Next, the code declares the property of `parentItem` in its component class, in this case `AppComponent`.
In this example the type of `childItem` is `string`, so `parentItem` needs to be a string.
Here, `parentItem` has the string value of `lamp`.
As you can see here, the `parentItem` in `AppComponent` is a string, which the `ItemDetailComponent` expects:
<code-example path="property-binding/src/app/app.component.ts" region="parent-data-type" header="src/app/app.component.ts"></code-example> <code-example path="property-binding/src/app/app.component.ts" region="parent-data-type" header="src/app/app.component.ts"></code-example>
### Passing in an object With this configuration, the view of `<app-item-detail>` uses the value of `lamp` for `childItem`.
The previous simple example showed passing in a string. To pass in an object, ## Property binding and security
the syntax and thinking are the same.
In this scenario, `ItemListComponent` is nested within `AppComponent` and the `items` property expects an array of objects. Property binding can help keep content secure.
For example, consider the following malicious content.
<code-example path="property-binding/src/app/app.component.html" region="pass-object" header="src/app/app.component.html"></code-example>
The `items` property is declared in the `ItemListComponent` with a type of `Item` and decorated with `@Input()`:
<code-example path="property-binding/src/app/item-list/item-list.component.ts" region="item-input" header="src/app/item-list.component.ts"></code-example>
In this sample app, an `Item` is an object that has two properties; an `id` and a `name`.
<code-example path="property-binding/src/app/item.ts" region="item-class" header="src/app/item.ts"></code-example>
While a list of items exists in another file, `mock-items.ts`, you can
specify a different item in `app.component.ts` so that the new item will render:
<code-example path="property-binding/src/app/app.component.ts" region="pass-object" header="src/app.component.ts"></code-example>
You just have to make sure, in this case, that you're supplying an array of objects because that's the type of `Item` and is what the nested component, `ItemListComponent`, expects.
In this example, `AppComponent` specifies a different `item` object
(`currentItems`) and passes it to the nested `ItemListComponent`. `ItemListComponent` was able to use `currentItems` because it matches what an `Item` object is according to `item.ts`. The `item.ts` file is where
`ItemListComponent` gets its definition of an `item`.
## Remember the brackets
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:
<code-example path="property-binding/src/app/app.component.html" region="no-evaluation" header="src/app.component.html"></code-example>
Omitting the brackets will render the string
`parentItem`, not the value of `parentItem`.
## 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 put directly into the template.
* This initial value never changes.
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 `StringInitComponent` to a fixed string,
not a template expression. Angular sets it and forgets about it.
<code-example path="property-binding/src/app/app.component.html" region="string-init" header="src/app/app.component.html"></code-example>
The `[item]` binding, on the other hand, remains a live binding to the component's `currentItems` property.
## Property binding vs. interpolation
You often have a choice between interpolation and property binding.
The following binding pairs do the same thing:
<code-example path="property-binding/src/app/app.component.html" region="property-binding-interpolation" header="src/app/app.component.html"></code-example>
Interpolation is a convenient alternative to property binding in
many cases. When rendering data values as strings, there is no
technical reason to prefer one form to the other, though readability
tends to favor interpolation. However, *when setting an element
property to a non-string data value, you must use property binding*.
## Content security
Imagine the following malicious content.
<code-example path="property-binding/src/app/app.component.ts" region="malicious-content" header="src/app/app.component.ts"></code-example> <code-example path="property-binding/src/app/app.component.ts" region="malicious-content" header="src/app/app.component.ts"></code-example>
In the component template, the content might be used with interpolation: The component template interpolates the content as follows:
<code-example path="property-binding/src/app/app.component.html" region="malicious-interpolated" header="src/app/app.component.html"></code-example> <code-example path="property-binding/src/app/app.component.html" region="malicious-interpolated" header="src/app/app.component.html"></code-example>
Fortunately, Angular data binding is on alert for dangerous HTML. In the above case, The browser doesn't process the HTML and instead displays it raw, as follows.
the HTML displays as is, and the Javascript does not execute. Angular **does not**
allow HTML with script tags to leak into the browser, neither with interpolation
nor property binding.
In the following example, however, Angular [sanitizes](guide/security#sanitization-and-security-contexts)
the values before displaying them.
<code-example path="property-binding/src/app/app.component.html" region="malicious-content" header="src/app/app.component.html"></code-example>
Interpolation handles the `<script>` tags differently than
property binding but both approaches render the
content harmlessly. The following is the browser output
of the `evilTitle` examples.
<code-example language="bash"> <code-example language="bash">
"Template &lt;script&gt;alert("evil never sleeps")&lt;/script&gt; Syntax" is the interpolated evil title. "Template &lt;script&gt;alert("evil never sleeps")&lt;/script&gt; Syntax" is the interpolated evil title.
</code-example>
Angular does not allow HTML with `<script>` tags, neither with [interpolation](guide/interpolation) nor property binding, which prevents the JavaScript from running.
In the following example, however, Angular [sanitizes](guide/security#sanitization-and-security-contexts) the values before displaying them.
<code-example path="property-binding/src/app/app.component.html" region="malicious-content" header="src/app/app.component.html"></code-example>
Interpolation handles the `<script>` tags differently than property binding, but both approaches render the content harmlessly.
The following is the browser output of the sanitized `evilTitle` example.
<code-example language="bash">
"Template Syntax" is the property bound evil title. "Template Syntax" is the property bound evil title.
</code-example> </code-example>
## Property binding and interpolation
Often [interpolation](guide/interpolation) and property binding can achieve the same results.
The following binding pairs do the same thing.
<code-example path="property-binding/src/app/app.component.html" region="property-binding-interpolation" header="src/app/app.component.html"></code-example>
You can use either form when rendering data values as strings, though interpolation is preferable for readability.
However, when setting an element property to a non-string data value, you must use property binding.
<hr />
## What's next
* [Property binding best practices](guide/property-binding-best-practices)

View File

@ -422,6 +422,11 @@
"title": "Keeping Up-to-Date", "title": "Keeping Up-to-Date",
"tooltip": "Information about updating Angular applications and libraries to the latest version." "tooltip": "Information about updating Angular applications and libraries to the latest version."
}, },
{
"url": "guide/property-binding-best-practices",
"title": "Property Binding Best Practices",
"tooltip": "Use property binding efficiently."
},
{ {
"title": "Testing", "title": "Testing",
"tooltip": "Testing your Angular apps.", "tooltip": "Testing your Angular apps.",