docs: re-write interpolation section and add example (#25170)

PR Close #25170
This commit is contained in:
Kapunahele Wong 2018-07-19 15:20:27 -04:00 committed by Andrew Kushnir
parent 7bafe180fb
commit d2eea769f6
16 changed files with 360 additions and 80 deletions

View File

@ -0,0 +1,47 @@
import { browser, element, by } from 'protractor';
describe('Interpolation e2e tests', () => {
beforeEach(function () {
browser.get('');
});
it('should display Interpolation and Template Expressions', function () {
expect(element(by.css('h1')).getText()).toEqual('Interpolation and Template Expressions');
});
it('should display Current customer: Maria', function () {
expect(element.all(by.css('h3')).get(0).getText()).toBe(`Current customer: Maria`);
});
it('should display The sum of 1 + 1 is not 4.', function () {
expect(element.all(by.css('p:last-child')).get(0).getText()).toBe(`The sum of 1 + 1 is not 4.`);
});
it('should display Expression Context', function () {
expect(element.all(by.css('h2')).get(1).getText()).toBe(`Expression Context`);
});
it('should display a list of customers', function () {
expect(element.all(by.css('li')).get(0).getText()).toBe(`Maria`);
});
it('should display two pictures', function() {
let pottedPlant = element.all(by.css('img')).get(0);
let lamp = element.all(by.css('img')).get(1);
expect(pottedPlant.getAttribute('src')).toContain('pottedPlant');
expect(pottedPlant.isDisplayed()).toBe(true);
expect(lamp.getAttribute('src')).toContain('lamp');
expect(lamp.isDisplayed()).toBe(true);
});
it('should support user input', function () {
let input = element(by.css('input'));
let label = element(by.css('label'));
expect(label.getText()).toEqual('Type something:');
input.sendKeys('abc');
expect(label.getText()).toEqual('Type something: abc');
});
});

View File

@ -0,0 +1,59 @@
<div>
<h1>Interpolation and Template Expressions</h1>
<hr />
<div>
<h2>Interpolation</h2>
<!-- #docregion interpolation-example1 -->
<h3>Current customer: {{ currentCustomer }}</h3>
<!-- #enddocregion interpolation-example1 -->
<!-- #docregion component-property -->
<p>{{title}}</p>
<div><img src="{{itemImageUrl}}"></div>
<!-- #enddocregion component-property -->
<h3>Evaluating template expressions </h3>
<h4>Simple evaluation (to a string):</h4>
<!-- #docregion convert-string -->
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}.</p>
<!-- #enddocregion convert-string -->
<h4>Evaluates using a method (also evaluates to a string):</h4>
<!-- #docregion invoke-method -->
<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>
<!-- #enddocregion invoke-method -->
</div>
<hr />
<h2>Expression Context</h2>
<div>
<h3>Component context, properties of app.component.ts:</h3>
<!-- #docregion component-context -->
<h4>{{recommended}}</h4>
<img [src]="itemImageUrl2">
<!-- #enddocregion component-context -->
</div>
<div>
<h4>Template context, template input variables (let customer):</h4>
<!-- #docregion template-input-variable -->
<ul>
<li *ngFor="let customer of customers">{{customer.name}}</li>
</ul>
<!-- #enddocregion template-input-variable -->
</div>
<div (keyup)="0">
<h4>Template context: template reference variables (#customerInput):</h4>
<label>Type something:
<!-- #docregion template-reference-variable -->
<input #customerInput>{{customerInput.value}}</label>
<!-- #enddocregion template-reference-variable -->
</div>
</div>

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'Featured product:'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('Featured product:');
}));
it('should render title in a p tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('p').textContent).toContain('Featured product:');
}));
});

View File

@ -0,0 +1,25 @@
import { Component } from '@angular/core';
import { CUSTOMERS } from './customers';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
customers = CUSTOMERS;
currentCustomer = 'Maria';
title = 'Featured product:';
itemImageUrl = '../assets/pottedPlant.png';
recommended = 'You might also like:';
itemImageUrl2 = '../assets/lamp.png';
getVal(): number { return 2; }
}

View File

@ -0,0 +1,18 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,3 @@
export class Customer {
name: string;
}

View File

@ -0,0 +1,9 @@
import { Customer } from './customer';
export const CUSTOMERS: Customer[] = [
{ name: 'Maria' },
{ name: 'Oliver' },
{ name: 'Walter' },
{ name: 'Lakshmi' },
{ name: 'Yasha' }
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Interpolation</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Interpolation",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["interpolation"]
}

View File

@ -0,0 +1,30 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
itemImageUrl = '../assets/lamp.png';
isUnchanged = true;
classes = 'special';
// #docregion parent-data-type
parentItem = 'bananas';
// #enddocregion parent-data-type
// #docregion pass-object
currentItem = [{
id: 21,
name: 'peaches'
}];
// #enddocregion pass-object
interpolationTitle = 'Interpolation';
propertyTitle = 'Property binding';
// #docregion malicious-content
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
// #enddocregion malicious-content
}

View File

@ -42,31 +42,46 @@ Begin with the first form of data binding&mdash;interpolation&mdash;to see how m
{@a interpolation} {@a interpolation}
## Interpolation ( <span class="syntax">{&#xfeff;{...}}</span> ) ## Interpolation and Template Expressions
You met the double-curly braces of interpolation, `{{` and `}}`, early in your Angular education. Interpolation allows you to incorporate calculated strings into the text
between HTML element tags and within attribute assignments. Template
expressions are what you use to calculate those strings.
<code-example path="template-syntax/src/app/app.component.html" region="first-interpolation" header="src/app/app.component.html" linenums="false"> The interpolation <live-example></live-example> demonstrates all of
the syntax and code snippets described in this section.
### Interpolation `{{...}}`
Interpolation refers to embedding expressions into marked up text.
By default, interpolation uses as its delimiter the double curly braces, `{{` and `}}`.
In the following snippet, `{{ currentCustomer }}` is an example of interpolation.
<code-example path="interpolation/src/app/app.component.html" region="interpolation-example1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
You use interpolation to weave calculated strings into the text between HTML element tags and within attribute assignments. The text between the braces is often the name of a component
property. Angular replaces that name with the
string value of the corresponding component property.
<code-example path="template-syntax/src/app/app.component.html" region="title+image" header="src/app/app.component.html" linenums="false"> <code-example path="interpolation/src/app/app.component.html" region="component-property" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
The text between the braces is often the name of a component property. Angular replaces that name with the In the example above, Angular evaluates the `title` and `itemImageUrl` properties
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 some title text and then an image.
and "fills in the blanks", first displaying a bold application title and then a heroic image.
More generally, the text between the braces is a **template expression** that Angular first **evaluates** More generally, the text between the braces is a **template expression**
and then **converts to a string**. The following interpolation illustrates the point by adding the two numbers: that Angular first **evaluates** and then **converts to a string**.
The following interpolation illustrates the point by adding two numbers:
<code-example path="template-syntax/src/app/app.component.html" region="sum-1" header="src/app/app.component.html" linenums="false"> <code-example path="interpolation/src/app/app.component.html" region="convert-string" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
The expression can invoke methods of the host component such as `getVal()`, seen here: The expression can invoke methods of the host component such as `getVal()` in
the following example:
<code-example path="template-syntax/src/app/app.component.html" region="sum-2" header="src/app/app.component.html" linenums="false"> <code-example path="interpolation/src/app/app.component.html" region="invoke-method" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
Angular evaluates all expressions in double curly braces, Angular evaluates all expressions in double curly braces,
@ -74,60 +89,67 @@ converts the expression results to strings, and links them with neighboring lite
it assigns this composite interpolated result to an **element or directive property**. 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. 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](guide/template-syntax#property-binding), as is explained [below](guide/template-syntax#property-binding-or-interpolation).
But first, let's take a closer look at template expressions and statements. <div class="alert is-helpful">
However, interpolation is a special syntax that Angular converts into a
property binding.
If you'd like to use something other than `{{` and `}}`, you can
configure the interpolation delimiter via the
[interpolation](api/core/Component#interpolation)
option in the `Component` metadata.
<hr/> </div>
{@a template-expressions} ### Template expressions
## Template expressions A template **expression** produces a value and appears within the double
curly braces, `{{ }}`.
A template **expression** produces a value.
Angular executes the expression and assigns it to a property of a binding target; 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 target could be an HTML element, a component, or a directive.
The interpolation braces in `{{1 + 1}}` surround the template expression `1 + 1`. The interpolation braces in `{{1 + 1}}` surround the template expression `1 + 1`.
In the [property binding](guide/template-syntax#property-binding) section below, In the property binding,
a template expression appears in quotes to the right of the&nbsp;`=` symbol as in `[property]="expression"`. a template expression appears in quotes to the right of the&nbsp;`=` symbol as in `[property]="expression"`.
You write these template expressions in a language that looks like JavaScript. In terms of syntax, template expressions are similar to JavaScript.
Many JavaScript expressions are legal template expressions, but not all. Many JavaScript expressions are legal template expressions, with a few exceptions.
JavaScript expressions that have or promote side effects are prohibited, You can't use JavaScript expressions that have or promote side effects,
including: including:
* assignments (`=`, `+=`, `-=`, ...) * Assignments (`=`, `+=`, `-=`, `...`)
* <code>new</code> * Operators such as `new`, `typeof`, `instanceof`, etc.
* chaining expressions with <code>;</code> or <code>,</code> * Chaining expressions with <code>;</code> or <code>,</code>
* increment and decrement operators (`++` and `--`) * The increment and decrement operators `++` and `--`
* Some of the ES2015+ operators
Other notable differences from JavaScript syntax include: Other notable differences from JavaScript syntax include:
* no support for the bitwise operators `|` and `&` * No support for the bitwise operators such as `|` and `&`
* new [template expression operators](guide/template-syntax#expression-operators), such as `|`, `?.` and `!`. * New template expression operators, such as `|`, `?.` and `!`
<!-- link to: guide/template-syntax#expression-operators -->
{@a expression-context}
### Expression context ### Expression context
The *expression context* is typically the _component_ instance. The *expression context* is typically the _component_ instance.
In the following snippets, the `title` within double-curly braces and the In the following snippets, the `recommended` within double curly braces and the
`isUnchanged` in quotes refer to properties of the `AppComponent`. `itemImageUrl2` in quotes refer to properties of the `AppComponent`.
<code-example path="template-syntax/src/app/app.component.html" region="context-component-expression" header="src/app/app.component.html" linenums="false"> <code-example path="interpolation/src/app/app.component.html" region="component-context" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
An expression may also refer to properties of the _template's_ context An expression may also refer to properties of the _template's_ context
such as a [template input variable](guide/template-syntax#template-input-variable) (`let hero`) such as a template input variable,
or a [template reference variable](guide/template-syntax#ref-vars) (`#heroInput`). <!-- link to built-in-directives#template-input-variables -->
`let customer`, or a template reference variable, `#customerInput`.
<!-- link to guide/template-ref-variables -->
<code-example path="template-syntax/src/app/app.component.html" region="context-var" header="src/app/app.component.html" linenums="false"> <code-example path="interpolation/src/app/app.component.html" region="template-input-variable" header="src/app/app.component.html (template input variable)" linenums="false">
</code-example>
<code-example path="interpolation/src/app/app.component.html" region="template-reference-variable" header="src/app/app.component.html (template reference variable)" linenums="false">
</code-example> </code-example>
The context for terms in an expression is a blend of the _template variables_, The context for terms in an expression is a blend of the _template variables_,
@ -136,34 +158,32 @@ 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_, the template variable name takes precedence, followed by a name in the directive's _context_,
and, lastly, the component's member names. and, lastly, the component's member names.
The previous example presents such a name collision. The component has a `hero` The previous example presents such a name collision. The component has a `customer`
property and the `*ngFor` defines a `hero` template variable. property and the `*ngFor` defines a `customer` template variable.
The `hero` in `{{hero.name}}`
<div class="alert is-helpful">
The `customer` in `{{customer.name}}`
refers to the template input variable, not the component's property. refers to the template input variable, not the component's property.
Template expressions cannot refer to anything in Template expressions cannot refer to anything in
the global namespace (except `undefined`). They can't refer to `window` or `document`. They the global namespace, except `undefined`. They can't refer to
can't call `console.log` or `Math.max`. They are restricted to referencing `window` or `document`. Additionally, they
can't call `console.log()` or `Math.max()` and they are restricted to referencing
members of the expression context. members of the expression context.
</div>
{@a no-side-effects}
{@a expression-guidelines}
### Expression guidelines ### Expression guidelines
Template expressions can make or break an application. When using template expressions follow these guidelines:
Please follow these guidelines:
* [No visible side effects](guide/template-syntax#no-visible-side-effects) * [No visible side effects](guide/template-syntax#no-visible-side-effects)
* [Quick execution](guide/template-syntax#quick-execution) * [Quick execution](guide/template-syntax#quick-execution)
* [Simplicity](guide/template-syntax#simplicity) * [Simplicity](guide/template-syntax#simplicity)
* [Idempotence](guide/template-syntax#idempotence)
The only exceptions to these guidelines should be in specific circumstances that you thoroughly understand.
#### No visible side effects ### No visible side effects
A template expression should not change any application state other than the value of the A template expression should not change any application state other than the value of the
target property. target property.
@ -172,37 +192,43 @@ 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. 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. The view should be stable throughout a single rendering pass.
#### Quick execution 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.
<div class="alert is-helpful">
There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects that when iterating over them.
For more information, see the [*ngFor with `trackBy`](guide/template-syntax#ngfor-with-trackby) section of this guide.
</div>
### Quick execution
Angular executes template expressions after every change detection cycle. Angular executes template expressions after every change detection cycle.
Change detection cycles are triggered by many asynchronous activities such as Change detection cycles are triggered by many asynchronous activities such as
promise resolutions, http results, timer events, keypresses and mouse moves. promise resolutions, HTTP results, timer events, key presses and mouse moves.
Expressions should finish quickly or the user experience may drag, especially on slower devices. Expressions should finish quickly or the user experience may drag, especially on slower devices.
Consider caching values when their computation is expensive. Consider caching values when their computation is expensive.
#### Simplicity ### Simplicity
Although it's possible to write quite complex template expressions, you should avoid them. Although it's possible to write complex template expressions, it's a better
practice to avoid them.
A property name or method call should be the norm. A property name or method call should be the norm, but an occasional Boolean negation, `!`, is OK.
An occasional Boolean negation (`!`) is OK. Otherwise, confine application and business logic to the component,
Otherwise, confine application and business logic to the component itself, where it is easier to develop and test.
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.
<!-- end of Interpolation doc -->
<hr/> <hr/>
@ -1673,8 +1699,8 @@ You can only bind to _another_ component or directive through its _Input_ and _O
Remember that all **components** are **directives**. Remember that all **components** are **directives**.
The following discussion refers to _components_ for brevity and The following discussion refers to _components_ for brevity and
because this topic is mostly a concern for component authors. because this topic is mostly a concern for component authors.
</div> </div>
<h3 class="no-toc">Discussion</h3> <h3 class="no-toc">Discussion</h3>
@ -1985,7 +2011,7 @@ You'll need this template operator when you turn on strict null checks. It's opt
{@a any-type-cast-function} {@a any-type-cast-function}
## The `$any` type cast function (`$any( <expression> )`) ## The `$any` type cast function (`$any( <expression> )`)
Sometimes a binding expression will be reported as a type error and it is not possible or difficult Sometimes a binding expression will be reported as a type error and it is not possible or difficult
to fully specify the type. To silence the error, you can use the `$any` cast function to cast to fully specify the type. To silence the error, you can use the `$any` cast function to cast
@ -1994,7 +2020,7 @@ the expression to [the `any` type](http://www.typescriptlang.org/docs/handbook/b
<code-example path="template-syntax/src/app/app.component.html" region="any-type-cast-function-1" header="src/app/app.component.html" linenums="false"> <code-example path="template-syntax/src/app/app.component.html" region="any-type-cast-function-1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
In this example, when the Angular compiler turns your template into TypeScript code, In this example, when the Angular compiler turns your template into TypeScript code,
it prevents TypeScript from reporting that `marker` is not a member of the `Hero` it prevents TypeScript from reporting that `marker` is not a member of the `Hero`
interface. interface.