1899 lines
69 KiB
Markdown
1899 lines
69 KiB
Markdown
@title
|
|
Template Syntax
|
|
|
|
@intro
|
|
Learn how to write templates that display data and consume user events with the help of data binding.
|
|
|
|
@description
|
|
|
|
<style>
|
|
h4 {font-size: 17px !important; text-transform: none !important;}
|
|
.syntax { font-family: Consolas, 'Lucida Sans', Courier, sans-serif; color: black; font-size: 85%; }
|
|
h4 .syntax { font-size: 100%; }
|
|
|
|
</style>
|
|
|
|
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.
|
|
|
|
|
|
{@a toc}
|
|
### Table of contents
|
|
This guide covers the basic elements of the Angular template syntax, elements you'll need to construct the view:
|
|
|
|
* [HTML in templates](#html)
|
|
* [Interpolation ( <span class="syntax">{{...}}</span> )](#interpolation)
|
|
* [Template expressions](#template-expressions)
|
|
* [Template statements](#template-statements)
|
|
* [Binding syntax](#binding-syntax)
|
|
* [Property binding ( <span class="syntax">[property]</span> )](#property-binding)
|
|
* [Attribute, class, and style bindings](#other-bindings)
|
|
* [Event binding ( <span class="syntax">(event)</span> )](#event-binding)
|
|
* [Two-way data binding ( <span class="syntax">[(...)]</span> )](#two-way)
|
|
* [Built-in directives](#directives)
|
|
* [Attribute directives](#attribute-directives)
|
|
* [NgClass](#ngClass)
|
|
* [NgStyle](#ngStyle)
|
|
* [NgModel (<span class="syntax">[(ngModel)]</span>) ](#ngModel)
|
|
* [Structural directives](#structural-directives)
|
|
* [NgIf](#ngIf)
|
|
* [NgFor](#ngFor)
|
|
* [Template input variables](#template-input-variables)
|
|
* [microsyntax](#microsyntax)
|
|
* [The NgSwitch directives](#ngSwitch)
|
|
* [Template reference variables ( <span class="syntax">#var</span> )](#ref-vars)
|
|
* [Input and output properties ( <span class="syntax">@Input</span> and <span class="syntax">@Output</span> )](#inputs-outputs)
|
|
* [Template expression operators](#expression-operators)
|
|
* [pipe ( <span class="syntax">|</span> )](#pipe)
|
|
* [safe navigation operator ( <span class="syntax">?.</span> )](#safe-navigation-operator)
|
|
|
|
The <live-example></live-example>
|
|
demonstrates all of the syntax and code snippets described in this guide.
|
|
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a html}
|
|
## HTML in templates
|
|
HTML is the language of the Angular template.
|
|
The [QuickStart](../quickstart.html) application has a template that is pure HTML:
|
|
|
|
<code-example language="html" escape="html">
|
|
<h1>Hello Angular</h1>
|
|
|
|
</code-example>
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Begin with the first form of data binding—interpolation—to see how much richer template HTML can be.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a interpolation}
|
|
## Interpolation ( <span class="syntax">{{...}}</span> )
|
|
You met the double-curly braces of interpolation, `{{` and `}}`, early in your Angular education.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='first-interpolation'}
|
|
|
|
You use interpolation to weave calculated strings into the text between HTML element tags and within attribute assignments.
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='title+image'}
|
|
|
|
The material 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 material 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 within braces:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='sum-1'}
|
|
|
|
The expression can invoke methods of the host component with `getVal()` as seen here:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='sum-2'}
|
|
|
|
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](#property-binding), and is explained below.
|
|
|
|
But first, let's take a closer look at template expressions and statements.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a template-expressions}
|
|
## Template expressions
|
|
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](#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} expressions that have or promote side effects are prohibited,
|
|
including:
|
|
|
|
* assignments (`=`, `+=`, `-=`, ...)
|
|
* !{__new_op}
|
|
* chaining expressions with !{__chaining_op}
|
|
* increment and decrement operators (`++` and `--`)
|
|
Other notable differences from #{_JavaScript} syntax include:
|
|
|
|
<h3 id='expression-context'>
|
|
Expression context
|
|
</h3>
|
|
|
|
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`.
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='context-component-expression'}
|
|
|
|
An expression may also refer to properties of the _template's_ context
|
|
such as a [template input variable](#template-input-variable) (`let hero`)
|
|
or a [template reference variable](#ref-vars) (`#heroInput`).
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='context-var'}
|
|
|
|
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}}` refers to the template input variable, not the component's property.
|
|
<a href="#toc">back to top</a>
|
|
|
|
{@a no-side-effects}
|
|
|
|
|
|
{@a expression-guidelines}
|
|
### Expression guidelines
|
|
Template expressions can make or break an application.
|
|
Please follow these guidelines:
|
|
|
|
* [No visible side effects](#no-visible-side-effects)
|
|
* [Quick execution](#quick-execution)
|
|
* [Simplicity](#simplicity)
|
|
* [Idempotence](#idempotence)
|
|
|
|
The only exceptions to these guidelines should be in specific circumstances that you thoroughly understand.
|
|
|
|
#### No visible side effects
|
|
|
|
A template expression should not change any application state other than the value of the
|
|
target property.
|
|
|
|
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.
|
|
|
|
#### Quick execution
|
|
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 computed from other values when the 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.
|
|
|
|
#### Idempotence
|
|
An [idempotent](https://en.wikipedia.org/wiki/Idempotence) expression is ideal because
|
|
it is free of side effects and improves Angular's change detection performance.
|
|
|
|
In Angular terms, an idempotent expression always returns *exactly the same thing* until
|
|
one of its dependent values changes.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.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a template-statements}
|
|
## Template statements
|
|
|
|
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](#event-binding) section,
|
|
appearing in quotes to the right of the `=` symbol as in `(event)="statement"`.
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='context-component-statement'}
|
|
|
|
A template statement *has a side effect*.
|
|
That's the whole point of an event.
|
|
It's how you update application state from user action.
|
|
|
|
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 is different than the template expression parser and
|
|
specifically supports both basic assignment (`=`) and chaining expressions
|
|
(with !{__chaining_op}).
|
|
|
|
However, certain #{_JavaScript} syntax is not allowed:
|
|
* !{__new_op}
|
|
* increment and decrement operators, `++` and `--`
|
|
* operator assignment, such as `+=` and `-=`
|
|
* the bitwise operators `|` and `&`
|
|
* the [template expression operators](#expression-operators)
|
|
### Statement context
|
|
|
|
As with expressions, statements can refer only to what's in the statement context
|
|
such as an event handling method of the component instance.
|
|
The *statement context* is typically the component instance.
|
|
The *deleteHero* in `(click)="deleteHero()"` is a method of the data-bound component.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='context-component-statement'}
|
|
|
|
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](#template-input-variable) (`let hero`),
|
|
and a [template reference variable](#ref-vars) (`#heroForm`)
|
|
are passed to an event handling method of the component.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='context-var-statement'}
|
|
|
|
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.
|
|
### Statement guidelines
|
|
|
|
As with expressions, avoid writing complex template statements.
|
|
A method call or simple property assignment should be the norm.
|
|
|
|
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.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a binding-syntax}
|
|
## Binding syntax: An overview
|
|
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.
|
|
|
|
Angular provides many kinds of data binding.
|
|
This guide covers most of them, after a high-level view of Angular data binding and its syntax.
|
|
|
|
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_.
|
|
:
|
|
<style>
|
|
td, th {vertical-align: top}
|
|
</style>
|
|
|
|
|
|
<table width="100%">
|
|
|
|
<col width="30%">
|
|
|
|
</col>
|
|
|
|
|
|
<col width="50%">
|
|
|
|
</col>
|
|
|
|
|
|
<col width="20%">
|
|
|
|
</col>
|
|
|
|
|
|
<tr>
|
|
|
|
<th>
|
|
Data direction
|
|
</th>
|
|
|
|
|
|
<th>
|
|
Syntax
|
|
</th>
|
|
|
|
|
|
<th>
|
|
Type
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
One-way<br>from data source<br>to view target
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
<code-example>
|
|
{{expression}}
|
|
[target] = "expression"
|
|
bind-target = "expression"
|
|
</code-example>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Interpolation<br>
|
|
Property<br>
|
|
Attribute<br>
|
|
Class<br>
|
|
Style
|
|
</td>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
One-way<br>from view target<br>to data source
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
<code-example>
|
|
(target) = "statement"
|
|
on-target = "statement"
|
|
</code-example>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Event
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Two-way
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
<code-example>
|
|
[(target)] = "expression"
|
|
bindon-target = "expression"
|
|
</code-example>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Two-way
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
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
|
|
|
|
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*.
|
|
|
|
It really *is* HTML Plus.
|
|
But it's also significantly different than the HTML you're used to.
|
|
It requires a new mental model.
|
|
|
|
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.
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='img+button'}
|
|
|
|
You still create a structure and initialize attribute values this way in Angular templates.
|
|
|
|
Then you learn to create new elements with components that encapsulate HTML
|
|
and drop them into templates as if they were native HTML elements.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='hero-detail-1'}
|
|
|
|
That's HTML Plus.
|
|
|
|
Then you learn about data binding. The first binding you meet might look like this:
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='disabled-button-1'}
|
|
|
|
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.
|
|
|
|
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 vs. DOM property
|
|
|
|
The distinction between an HTML attribute and a DOM property is crucial to understanding how Angular binding works.
|
|
|
|
**Attributes are defined by HTML. Properties are defined by the DOM (Document Object Model).**
|
|
|
|
* A few HTML attributes have 1:1 mapping to properties. `id` is one example.
|
|
|
|
* Some HTML attributes don't have corresponding properties. `colspan` is one example.
|
|
|
|
* Some DOM properties don't have corresponding attributes. `textContent` is one example.
|
|
|
|
* 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:
|
|
|
|
**Attributes *initialize* DOM properties and then they are done.
|
|
Property values can change; attribute values can't.**
|
|
|
|
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 `value` *property* becomes "Sally".
|
|
But the HTML `value` *attribute* remains unchanged as you discover if you ask the input element
|
|
about that attribute: `input.getAttribute('value') // returns "Bob"`
|
|
|
|
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 `disabled` *property* is
|
|
`false` by default so the button is enabled.
|
|
When you add the `disabled` *attribute*, its presence alone initializes the button's `disabled` *property* to `true`
|
|
so the button is disabled.
|
|
|
|
Adding and removing the `disabled` *attribute* 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>`.
|
|
|
|
Setting the button's `disabled` *property* (say, with an Angular binding) disables or enables the button.
|
|
The value of the *property* matters.
|
|
|
|
**The HTML attribute and the DOM property are not the same thing, even when they have the same name.**
|
|
This fact bears repeating:
|
|
**Template binding works with *properties* and *events*, not *attributes*.**
|
|
|
|
|
|
~~~ {.callout.is-helpful}
|
|
|
|
|
|
<header>
|
|
A world without attributes
|
|
</header>
|
|
|
|
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 events of the target object.
|
|
HTML attributes effectively disappear.
|
|
|
|
~~~
|
|
|
|
With this model firmly in mind, read on to learn about binding targets.
|
|
|
|
### 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:
|
|
|
|
<style>
|
|
td, th {vertical-align: top}
|
|
</style>
|
|
|
|
|
|
<table width="100%">
|
|
|
|
<col width="10%">
|
|
|
|
</col>
|
|
|
|
|
|
<col width="15%">
|
|
|
|
</col>
|
|
|
|
|
|
<col width="75%">
|
|
|
|
</col>
|
|
|
|
|
|
<tr>
|
|
|
|
<th>
|
|
Type
|
|
</th>
|
|
|
|
|
|
<th>
|
|
Target
|
|
</th>
|
|
|
|
|
|
<th>
|
|
Examples
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Property
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Element property<br>
|
|
Component property<br>
|
|
Directive property
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-syntax-1'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Event
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Element event<br>
|
|
Component event<br>
|
|
Directive event
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='event-binding-syntax-1'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Two-way
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Event and property
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='2-way-binding-syntax-1'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Attribute
|
|
</td>
|
|
|
|
|
|
<td>
|
|
Attribute
|
|
(the exception)
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='attribute-binding-syntax-1'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Class
|
|
</td>
|
|
|
|
|
|
<td>
|
|
<code>class</code> property
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='class-binding-syntax-1'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
Style
|
|
</td>
|
|
|
|
|
|
<td>
|
|
<code>style</code> property
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='style-binding-syntax-1'}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
</div>With this broad view in mind, you're ready to look at binding types in detail.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a property-binding}
|
|
## Property binding ( <span class="syntax">[property]</span> )
|
|
Write a template **property binding** to set a property of a view element.
|
|
The binding sets the property to the value of a [template expression](#template-expressions).
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-1'}
|
|
|
|
Another example is disabling a button when the component says that it `isUnchanged`:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-2'}
|
|
|
|
Another is setting a property of a directive:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-3'}
|
|
|
|
Yet another is setting the model property of a custom component (a great way
|
|
for parent and child components to communicate):
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-4'}
|
|
|
|
### 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 the element raises events, you can listen to them with an [event binding](#event-binding).
|
|
|
|
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](../api/core/index/ViewChild-decorator.html) and
|
|
[ContentChild](../api/core/index/ContentChild-decorator.html).
|
|
### Binding target
|
|
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.
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-1'}
|
|
|
|
Some people prefer the `bind-` prefix alternative, known as the *canonical form*:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-5'}
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-3'}
|
|
|
|
|
|
Technically, Angular is matching the name to a directive [input](#inputs-outputs),
|
|
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.If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error.
|
|
|
|
### Avoid side effects
|
|
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.
|
|
|
|
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.
|
|
|
|
### Return the proper type
|
|
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 `hero` property of the `HeroDetail` component expects a `Hero` object, which is exactly what you're sending in the property binding:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-4'}
|
|
|
|
### 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.
|
|
It does *not* evaluate the string!
|
|
|
|
Don't make the following mistake:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-6'}
|
|
|
|
|
|
|
|
{@a one-time-initialization}
|
|
### 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.
|
|
|
|
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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-7'}
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-vs-interpolation'}
|
|
|
|
_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.
|
|
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*.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.ts' region='evil-title'}
|
|
|
|
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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='property-binding-vs-interpolation-sanitization'}
|
|
|
|
Interpolation handles the script tags differently than property binding but both approaches render the
|
|
content harmlessly.
|
|
<figure class='image-display'>
|
|
<img src='/resources/images/devguide/template-syntax/evil-title.png' alt="evil title made safe" width='500px'> </img>
|
|
</figure>
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a other-bindings}
|
|
## Attribute, class, and style bindings
|
|
The template syntax provides specialized one-way bindings for scenarios less well suited to property binding.
|
|
|
|
### Attribute binding
|
|
You can set the value of an attribute directly with an **attribute binding**.
|
|
This is the only exception to the rule that a binding sets a target property. This is the only binding that creates and sets an attribute.
|
|
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?
|
|
|
|
**You must use attribute binding when there is no element property to bind.**
|
|
|
|
Consider the [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA),
|
|
[SVG](https://developer.mozilla.org/en-US/docs/Web/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.
|
|
<code-example language="html">
|
|
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
|
</code-example>
|
|
|
|
And you get this error:
|
|
<code-example format="nocode">
|
|
Template parse errors:
|
|
Can't bind to 'colspan' since it isn't a known native property
|
|
</code-example>
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Bind `[attr.colspan]` to a calculated value:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='attrib-binding-colspan'}
|
|
|
|
Here's how the table renders:
|
|
<table border="1px">
|
|
<tr><td colspan="2">One-Two</td></tr>
|
|
<tr><td>Five</td><td>Six</td></tr>
|
|
</table>
|
|
|
|
One of the primary use cases for attribute binding
|
|
is to set ARIA attributes, as in this example:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='attrib-binding-aria'}
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
### Class binding
|
|
|
|
You can add and remove CSS class names from an element's `class` attribute with
|
|
a **class binding**.
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='class-binding-1'}
|
|
|
|
You can replace that with a binding to a string of the desired class names; this is an all-or-nothing, replacement binding.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='class-binding-2'}
|
|
|
|
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}.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='class-binding-3'}
|
|
|
|
|
|
While this is a fine way to toggle a single class name,
|
|
the [NgClass directive](#ngClass) is usually preferred when managing multiple class names at the same time.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
### Style binding
|
|
|
|
You can set inline styles with a **style binding**.
|
|
|
|
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]`.
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='style-binding-1'}
|
|
|
|
Some style binding styles have a unit extension.
|
|
The following example conditionally sets the font size in “em” and “%” units .
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='style-binding-2'}
|
|
|
|
|
|
While this is a fine way to set a single style,
|
|
the [NgStyle directive](#ngStyle) is generally preferred when setting several inline styles at the same time.
|
|
|
|
Note that a _style property_ name can be written in either
|
|
[dash-case](glossary.html#dash-case), as shown above, or
|
|
[camelCase](glossary.html#camelcase), such as `fontSize`.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a event-binding}
|
|
## Event binding ( <span class="syntax">(event)</span> )
|
|
The bindings directives you've met so far flow data in one direction: **from a component to an element**.
|
|
|
|
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** within parentheses on the left of an equal sign, and a quoted
|
|
[template statement](#template-statements) on the right.
|
|
The following event binding listens for the button's click event, calling
|
|
the component's `onSave()` method whenever a click occurs:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='event-binding-1'}
|
|
|
|
### Target event
|
|
A **name between parentheses** — for example, `(click)` —
|
|
identifies the target event. In the following example, the target is the button's click event.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='event-binding-1'}
|
|
|
|
Some people prefer the `on-` prefix alternative, known as the **canonical form**:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='event-binding-2'}
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='event-binding-3'}
|
|
|
|
|
|
The `myClick` directive is further described in the section
|
|
on [aliasing input/output properties](#aliasing-io).
|
|
If the name fails to match an element event or an output property of a known directive,
|
|
Angular reports an “unknown directive” error.
|
|
|
|
### *$event* and event handling statements
|
|
In an event binding, Angular sets up an event handler for the target event.
|
|
|
|
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.
|
|
|
|
The binding conveys information about the event, including data values, through
|
|
an **event object named `$event`**.
|
|
|
|
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]( https://developer.mozilla.org/en-US/docs/Web/Events),
|
|
with properties such as `target` and `target.value`.
|
|
|
|
Consider this example:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='without-NgModel'}
|
|
|
|
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`.
|
|
|
|
To update the `name` property, the changed text is retrieved by following the path `$event.target.value`.
|
|
|
|
If the event belongs to a directive (recall that components are directives), `$event` has whatever shape the directive decides to produce.
|
|
|
|
|
|
{@a eventemitter}
|
|
|
|
|
|
{@a custom-event}
|
|
### Custom events with <span class="syntax">EventEmitter</span>
|
|
|
|
Directives typically raise custom events with an Angular [EventEmitter](../api/core/index/EventEmitter-class.html).
|
|
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.
|
|
|
|
Here are the pertinent excerpts from that `HeroDetailComponent`: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.
|
|
|
|
Now imagine a hosting parent component that binds to the `HeroDetailComponent`'s `deleteRequest` event.
|
|
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.
|
|
|
|
### Template statements have side effects
|
|
The `deleteHero` method has a side effect: it deletes a hero.
|
|
Template statement side effects are not just OK, but expected.
|
|
|
|
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.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a two-way}
|
|
## Two-way binding ( <span class="syntax">[(...)]</span> )
|
|
You often want to both display a data property and update that property when the user makes changes.
|
|
|
|
On the element side that takes a combination of setting a specific element property
|
|
and listening for an element change event.
|
|
|
|
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)`.
|
|
|
|
|
|
~~~ {.callout.is-important}
|
|
|
|
|
|
<header>
|
|
[( )] = banana in a box
|
|
</header>
|
|
|
|
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:
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/sizer.component.ts'}
|
|
|
|
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.
|
|
|
|
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.
|
|
Try it in the <live-example></live-example>.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Fortunately, the Angular [_NgModel_](#ngModel) directive is a bridge that enables two-way binding to form elements.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a directives}
|
|
## Built-in directives
|
|
|
|
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?
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='event-binding-1'}
|
|
|
|
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.
|
|
|
|
This segment reviews some of the most frequently used built-in directives,
|
|
classified as either [_attribute_ directives](#attribute-directives) or [_structural_ directives](#structural-directives).
|
|
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a attribute-directives}
|
|
## Built-in _attribute_ directives
|
|
|
|
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_](attribute-directives.html) guide.
|
|
Many Angular modules such the [`RouterModule`](router.html "Routing and Navigation")
|
|
and the [`FormsModule`](forms.html "Forms") have their own attribute directives.
|
|
This section is an introduction to the most commonly used attribute directives:
|
|
|
|
* [`NgClass`](#ngClass) - add and remove a set of CSS classes
|
|
* [`NgStyle`](#ngStyle) - add and remove a set of HTML styles
|
|
* [`NgModel`](#ngModel) - two-way data binding to an HTML form element
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a ngClass}
|
|
### NgClass
|
|
|
|
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](#class-binding) is a good way to add or remove a *single* class.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='class-binding-3a'}
|
|
|
|
To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice.
|
|
|
|
Try binding `ngClass` to a key:value control !{__objectAsMap}.
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.ts' region='setClasses'}
|
|
|
|
Adding an `ngClass` property binding to `currentClasses` sets the element's classes accordingly:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgClass-1'}
|
|
|
|
|
|
It's up to you to call `setCurrentClassess()`, both initially and when the dependent properties change.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a ngStyle}
|
|
### NgStyle
|
|
You can set inline styles dynamically, based on the state of the component.
|
|
With `NgStyle` you can set many inline styles simultaneously.
|
|
|
|
A [style binding](#style-binding) is an easy way to set a *single* style value.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgStyle-1'}
|
|
|
|
To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice.
|
|
|
|
Try binding `ngStyle` to a key:value control !{__objectAsMap}.
|
|
Each key of the object is a style name; its value is whatever is appropriate for that style.
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.ts' region='setStyles'}
|
|
|
|
Adding an `ngStyle` property binding to `currentStyles` sets the element's styles accordingly:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgStyle-2'}
|
|
|
|
|
|
It's up to you to call `setCurrentStyles()`, both initially and when the dependent properties change.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a ngModel}
|
|
### NgModel - Two-way binding to form elements with <span class="syntax">[(ngModel)]</span>
|
|
When developing data entry forms, you often both display a data property and
|
|
update that property when the user makes changes.
|
|
|
|
Two-way data binding with the `NgModel` directive makes that easy. Here's an example:#### Inside <span class="syntax">[(ngModel)]</span>
|
|
Looking back at the `firstName` binding, note that
|
|
you could have achieved the same result with separate bindings to
|
|
the `<input>` element's `value` property and `input` event.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='without-NgModel'}
|
|
|
|
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?
|
|
|
|
That `ngModel` directive hides these onerous details behind its own `ngModel` input and `ngModelChange` output properties.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgModel-3'}
|
|
|
|
|
|
The `ngModel` data property sets the element's value property and the `ngModelChange` event property
|
|
listens for changes to the element's value.
|
|
|
|
The details are specific to each kind of element and therefore the `NgModel` directive only works for an element
|
|
supported by a [ControlValueAccessor](../api/forms/index/ControlValueAccessor-interface.html)
|
|
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_](forms.html) 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](#two-way) and skip `NgModel` altogether.
|
|
The [`sizer` shown above](#two-way) is an example of this technique.
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgModel-1'}
|
|
|
|
Is `[(ngModel)]` all you need? Is there ever a reason to fall back to its expanded form?
|
|
|
|
The `[(ngModel)]` syntax can only _set_ a data-bound property.
|
|
If you need to do something more or something different, you can write the expanded form.
|
|
|
|
The following contrived example forces the input value to uppercase:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgModel-4'}
|
|
|
|
Here are all variations in action, including the uppercase version:
|
|
<figure class='image-display'>
|
|
<img src='/resources/images/devguide/template-syntax/ng-model-anim.gif' alt="NgModel variations"> </img>
|
|
</figure>
|
|
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a structural-directives}
|
|
## Built-in _structural_ directives
|
|
|
|
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.
|
|
|
|
The deep details of structural directives are covered in the
|
|
[_Structural Directives_](structural-directives.html) guide
|
|
where you'll learn:
|
|
* why you
|
|
[_prefix the directive name with an asterisk_ (\*)](structural-directives.html#asterisk "The * in *ngIf").
|
|
* to use [`<ng-container>`](structural-directives.html#ngcontainer "<ng-container>")
|
|
to group elements when there is no suitable host element for the directive.
|
|
* how to write your own structural directive.
|
|
* that you can only apply [one structural directive](structural-directives.html#one-per-element "one per host element") to an element.
|
|
|
|
_This_ section is an introduction to the common structural directives:
|
|
|
|
* [`NgIf`](#ngIf) - conditionally add or remove an element from the DOM
|
|
* [`NgFor`](#ngFor) - repeat a template for each item in a list
|
|
* [`NgSwitch`](#ngSwitch) - a set of directives that switch among alternative views
|
|
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a ngIf}
|
|
### NgIf
|
|
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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgIf-1'}
|
|
|
|
|
|
|
|
~~~ {.alert.is-critical}
|
|
|
|
Don't forget the asterisk (`*`) in front of `ngIf`.
|
|
|
|
~~~
|
|
|
|
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.
|
|
#### Show/hide is not the same thing
|
|
You can control the visibility of an element with a
|
|
[class](#class-binding) or [style](#style-binding) binding:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgIf-3'}
|
|
|
|
Hiding an element is quite different from removing an element with `NgIf`.
|
|
|
|
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.
|
|
|
|
When `NgIf` is `false`, Angular physically 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.
|
|
|
|
#### Guard against null objects
|
|
The `ngIf` directive is often used to guard against a null object.
|
|
Show/hide is useless as a guard.
|
|
Angular will throw an error if a nested expression tries to access a property of a null object,
|
|
whether its visible or not.
|
|
|
|
Here we see `NgIf` guarding two `<div>`s.
|
|
The `currentHero` name will appear only when there is a `currentHero`.
|
|
The `nullHero` will never be displayed.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgIf-2'}
|
|
|
|
|
|
See also the
|
|
[_safe navigation operator_](#safe-navigation-operator "Safe naviation operator (?.)")
|
|
described below.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a ngFor}
|
|
### NgFor
|
|
`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.
|
|
|
|
Here is an example of `NgFor` applied to a simple `<div>`:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgFor-1'}
|
|
|
|
You can also apply an `NgFor` to a component element, as in this example:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgFor-2'}
|
|
|
|
|
|
|
|
~~~ {.alert.is-critical}
|
|
|
|
Don't forget the asterisk (`*`) in front of `ngFor`.
|
|
|
|
~~~
|
|
|
|
The text assigned to `*ngFor` is the instruction that guides the repeater process.
|
|
|
|
|
|
{@a microsyntax}
|
|
#### *ngFor microsyntax
|
|
The string assigned to `*ngFor` is not a [template expression](#template-expressions).
|
|
It's a *microsyntax* — a little language of its own that Angular interprets.
|
|
The string `"let hero of heroes"` means:
|
|
|
|
> *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 `<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.
|
|
Learn about the _microsyntax_ in the [_Structural Directives_](structural-directives.html#microsyntax) guide.
|
|
|
|
|
|
{@a template-input-variable}
|
|
|
|
|
|
{@a template-input-variables}
|
|
### Template input variables
|
|
|
|
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 the `hero` element with 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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgFor-1-2'}
|
|
|
|
Learn more about _template input variables_ in the [_Structural Directives_](structural-directives.html#template-input-variable) guide.
|
|
|
|
#### *ngFor with _index_
|
|
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.
|
|
|
|
The next example captures the `index` in a variable named `i` and displays it with the hero name like this.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgFor-3'}
|
|
|
|
|
|
Learn about the other `NgFor` context values such as `last`, `even`, and `odd` in the [NgFor API reference](../api/common/index/NgFor-directive.html).
|
|
|
|
|
|
{@a trackBy}
|
|
#### *ngFor with _trackBy_
|
|
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.
|
|
|
|
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 `NgFor` _should_ track.
|
|
In this case, that value is the hero's `id`.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.ts' region='trackByHeroes'}
|
|
|
|
In the microsyntax expression, set `trackBy` to this method.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='trackBy'}
|
|
|
|
Here is an illustration of the _trackBy_ effect.
|
|
"Reset heroes" creates new heroes with the same `hero.id`s.
|
|
"Change ids" creates new heroes with new `hero.id`s.
|
|
* With no `trackBy`, both buttons trigger complete DOM element replacement.
|
|
* With `trackBy`, only changing the `id` triggers element replacement.
|
|
|
|
<figure class='image-display'>
|
|
<img src='/resources/images/devguide/template-syntax/ng-for-track-by-anim.gif' alt="trackBy"> </img>
|
|
</figure>
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a ngSwitch}
|
|
### The _NgSwitch_ directives
|
|
|
|
*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 actually a set of three, cooperating directives:
|
|
`NgSwitch`, `NgSwitchCase`, and `NgSwitchDefault` as seen in this example.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgSwitch'}
|
|
|
|
|
|
<figure class='image-display'>
|
|
<img src='/resources/images/devguide/template-syntax/switch-anim.gif' alt="trackBy"> </img>
|
|
</figure>
|
|
|
|
`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`.
|
|
`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` adds its element to the DOM when its bound value equals the switch value.
|
|
|
|
* `NgSwitchDefault` adds its element to the DOM when there is no selected `NgSwitchCase`.
|
|
|
|
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 `hero` [input property](#inputs-outputs "Input 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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='NgSwitch-div'}
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a template-reference-variable}
|
|
|
|
|
|
{@a ref-vars}
|
|
|
|
|
|
{@a ref-var}
|
|
## Template reference variables ( <span class="syntax">#var</span> )
|
|
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
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" target="_blank" title="MDN: Web Components">web component</a>.
|
|
|
|
Use the hash symbol (#) to declare a reference variable.
|
|
The `#phone` declares a `phone` variable on an `<input>` element.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='ref-var'}
|
|
|
|
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
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='ref-phone'}
|
|
|
|
### How a reference variable gets its value
|
|
|
|
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.
|
|
|
|
The following is a *simplified* version of the form example in the [Forms](forms.html) guide.
|
|
|
|
{@example 'template-syntax/ts/src/app/hero-form.component.html'}
|
|
|
|
A template reference variable, `heroForm`, appears three times in this example, separated
|
|
by a large amount of HTML.
|
|
What is the value of `heroForm`?
|
|
|
|
If Angular hadn't taken it over when you imported the `FormsModule`,
|
|
it would be the [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement).
|
|
The `heroForm` is actually a reference to an Angular [NgForm](../api/forms/index/NgForm-directive.html "API: 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.
|
|
|
|
### Template reference variable warning notes
|
|
|
|
A template _reference_ variable (`#phone`) is _not_ the same as a template _input_ variable (`let phone`)
|
|
such as you might see in an [`*ngFor`](#template-input-variable).
|
|
Learn the difference in the [_Structural Directives_](structural-directives.html#template-input-variable) 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.
|
|
|
|
You can use the `ref-` prefix alternative to `#`.
|
|
This example declares the `fax` variable as `ref-fax` instead of `#fax`.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='ref-fax'}
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a inputs-outputs}
|
|
## Input and output properties ( <span class="syntax">@Input</span> and <span class="syntax">@Output</span> )
|
|
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**.
|
|
|
|
|
|
~~~ {.alert.is-important}
|
|
|
|
Remember: All **components** are **directives**.
|
|
|
|
~~~
|
|
|
|
|
|
You're drawing a sharp 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*.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 (`=`).
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='io-1'}
|
|
|
|
They are *neither inputs nor outputs* of the component. They are **sources** for their bindings.
|
|
The targets are the native `<img>` and `<button>` elements.
|
|
|
|
Now look at a another snippet in which the `HeroDetailComponent` is the **target** of a binding on the _left_ of the equals (`=`).
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='io-2'}
|
|
|
|
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.
|
|
|
|
In the `HeroDetailComponent`, such properties are marked with decorators as input and output properties.
|
|
|
|
{@example 'template-syntax/ts/src/app/hero-detail.component.ts' region='input-output-1'}
|
|
|
|
|
|
Alternatively, you can identify members in the `inputs` and `outputs` #{_array}s
|
|
of the directive metadata, as in this example:
|
|
|
|
{@example 'template-syntax/ts/src/app/hero-detail.component.ts' region='input-output-2'}
|
|
|
|
<br>You can specify an input/output property either with a decorator or in a metadata #{_array}.
|
|
Don't do both!### Input or output?
|
|
|
|
*Input* properties usually receive data values.
|
|
*Output* properties expose event producers, such as `EventEmitter` objects.
|
|
|
|
The terms _input_ and _output_ reflect the perspective of the target directive.
|
|
<figure class='image-display'>
|
|
<img src='/resources/images/devguide/template-syntax/input-output.png' alt="Inputs and outputs"> </img>
|
|
</figure>
|
|
|
|
`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.
|
|
|
|
<h3 id='aliasing-io'>
|
|
Aliasing input/output properties
|
|
</h3>
|
|
|
|
Sometimes the public name of an input/output property should be different from the internal name.
|
|
|
|
This is frequently the case with [attribute directives](attribute-directives.html).
|
|
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`.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='myClick'}
|
|
|
|
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 the* `myClick` *alias* to
|
|
the directive's own `clicks` property.
|
|
|
|
You can specify the alias for the property name by passing it into the input/output decorator like this:
|
|
|
|
|
|
{@example 'template-syntax/ts/src/app/click.directive.ts' region='output-myClick'}
|
|
|
|
|
|
You can also alias property names in the `inputs` and `outputs` #{_array}s.
|
|
You write a colon-delimited (`:`) string with
|
|
the directive property name on the *left* and the public alias on the *right*:
|
|
|
|
{@example 'template-syntax/ts/src/app/click.directive.ts' region='output-myClick2'}
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a expression-operators}
|
|
## Template expression operators
|
|
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_.
|
|
|
|
|
|
{@a pipe}
|
|
### The pipe operator ( <span class="syntax">|</span> )
|
|
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](./pipes.html) 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 (`|`)**:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='pipes-1'}
|
|
|
|
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:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='pipes-2'}
|
|
|
|
And you can also [apply parameters](./pipes.html#parameterizing-a-pipe) to a pipe:
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='pipes-3'}
|
|
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a safe-navigation-operator}
|
|
### The safe navigation operator ( <span class="syntax">?.</span> ) and null property paths
|
|
|
|
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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='safe-2'}
|
|
|
|
What happens when the following data bound `title` property is null?
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='safe-1'}
|
|
|
|
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.
|
|
|
|
Suppose the template expression involves a property path, as in this next example
|
|
that displays the `firstName` of a null hero.
|
|
|
|
<code-example language="html">
|
|
The null hero's name is {{nullHero.firstName}}
|
|
|
|
</code-example>
|
|
|
|
Worse, the *entire view disappears*.
|
|
|
|
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.
|
|
|
|
While waiting for data, the view should render without complaint, and
|
|
the null property path should display as blank just as the `title` property does.
|
|
|
|
Unfortunately, the app crashes when the `currentHero` is null.
|
|
|
|
You could code around that problem with [*ngIf](#ngIf).
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='safe-4'}
|
|
|
|
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.
|
|
|
|
{@example 'template-syntax/ts/src/app/app.component.html' region='safe-6'}
|
|
|
|
It works perfectly with long property paths such as `a?.b?.c?.d`.
|
|
<a href="#toc">back to top</a>
|
|
<div class='l-hr'>
|
|
|
|
</div>
|
|
|
|
## Summary
|
|
You've completed this survey of template syntax.
|
|
Now it's time to put that knowledge to work on your own components and directives. |