docs: edit and add example for Template Expression Operators section of Template Syntax (#28087)

PR Close #28087
This commit is contained in:
Kapunahele Wong 2019-01-08 15:58:23 -05:00 committed by Andrew Kushnir
parent f41242f18e
commit 6c4d91297e
11 changed files with 277 additions and 84 deletions

View File

@ -0,0 +1,31 @@
import { browser, element, by } from 'protractor';
import { logging } from 'selenium-webdriver';
describe('Template Expression Operators', function () {
beforeAll(function () {
browser.get('');
});
it('should have title Inputs and Outputs', function () {
let title = element.all(by.css('h1')).get(0);
expect(title.getText()).toEqual('Template Expression Operators');
});
it('should display json data', function () {
let jsonDate = element.all(by.css('p')).get(4);
expect(jsonDate.getText()).toContain('1980');
});
it('should display $98', function () {
let jsonDate = element.all(by.css('p')).get(5);
expect(jsonDate.getText()).toContain('$98.00');
});
it('should display Telephone', function () {
let jsonDate = element.all(by.css('p')).get(6);
expect(jsonDate.getText()).toContain('Telephone');
});
});

View File

@ -0,0 +1,10 @@
.no-show-div {
background-color: #ecfdff;
border: 1px #444 solid;
padding: 1rem;
color: #444;
}
.error {
color: red;
}

View File

@ -0,0 +1,80 @@
<h1>{{title}}</h1>
<hr />
<h2 id="pipes">Pipes</h2>
<!-- #docregion uppercase-pipe-->
<p>Title through uppercase pipe: {{title | uppercase}}</p>
<!-- #enddocregion uppercase-pipe-->
<!-- #docregion pipe-chain-->
<!-- convert title to uppercase, then to lowercase -->
<p>Title through a pipe chain: {{title | uppercase | lowercase}}</p>
<!-- #enddocregion pipe-chain-->
<!-- #docregion date-pipe -->
<!-- pipe with configuration argument => "February 25, 1980" -->
<p>Manufacture date with date format pipe: {{item.manufactureDate | date:'longDate'}}</p>
<!-- #enddocregion date-pipe -->
<p>Manufacture date with uppercase pipe: {{(item.manufactureDate | date:'longDate') | uppercase}}</p>
<!-- #docregion json-pipe -->
<p>Item json pipe: {{item | json}}</p>
<!-- #enddocregion json-pipe -->
<!-- #docregion currency-pipe -->
<p>Price with currency pipe: {{item.price | currency:'USD'}}</p>
<!-- #enddocregion currency-pipe -->
<hr />
<h2>Safe navigation operator: <code>?</code> and <code>null</code></h2>
<!-- #docregion safe -->
<p>The item name is: {{item?.name}}</p>
<!-- #enddocregion safe -->
<!-- #docregion safe-null -->
<p>The nullItem name is: {{nullItem?.name}}</p>
<!-- #enddocregion safe-null -->
<hr />
<h2>Error because nullItem is null:</h2>
<!-- uncomment to see error in console -->
<!-- <p>The null item's name is {{nullItem.name}}</p> -->
<div>
<p>Uncomment above paragraph and see console log for error about nullItem.name:</p>
<p class="error">TypeError: Cannot read property 'name' of null</p>
</div>
<hr />
<h2>The div will not display and there's no error: </h2>
<div class="no-show-div">
<p>There's a child paragraph element in here that doesn't show. </p>
<p *ngIf="nullItem">The null item's name {{nullItem.name}}</p>
</div>
<hr />
<h2>Div shows but interpolation doesn't:</h2>
<div>
<h3>Using and (<code>&&</code>)</h3>
<p>The null item's name is: {{nullItem && nullItem.name}}</p>
</div>
<div>
<h3>Using safe navigation operator (<code>?</code>)</h3>
<!-- No item, no problem! -->
<p>The null item's name is: {{nullItem?.name}}</p>
</div>
<!-- non-null assertion operator -->
<hr />
<h2>Non-null assertion operator (<code>!</code>)</h2>
<div>
<!-- #docregion non-null -->
<!--No color, no error -->
<p *ngIf="item">The item's color is: {{item!.color}}</p>
<!-- #enddocregion non-null -->

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 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Template Expression Operators';
item = {
name : 'Telephone',
manufactureDate : new Date(1980, 1, 25),
price: 98
};
nullItem = null;
}

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,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Template Expression Operators Example</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>Loading...</app-root>
</body>
</html>

View File

@ -0,0 +1,11 @@
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);

View File

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

View File

@ -2121,119 +2121,93 @@ You can specify the alias for the property name by passing the alias name to the
## Template expression operators ## Template expression operators
The template expression language employs a subset of JavaScript syntax supplemented with a few special operators The Angular 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_. for specific scenarios. The next sections cover three of these operators:
* [pipe](guide/template-syntax#pipe)
* [safe navigation operator](guide/template-syntax#safe-navigation-operator)
* [non-null assertion operator](guide/template-syntax#non-null-assertion-operator)
{@a pipe} {@a pipe}
### The pipe operator ( <span class="syntax">|</span> ) ### The pipe operator (`|`)
The result of an expression might require some transformation before you're ready to use it in a binding. 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. For example, you might display a number as a currency, change text to uppercase, or filter a list and sort it.
Angular [pipes](guide/pipes) are a good choice for small transformations such as these.
Pipes are simple functions that accept an input value and return a transformed value. 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 (`|`)**: They're easy to apply within template expressions, using the pipe operator (`|`):
<code-example path="template-syntax/src/app/app.component.html" region="pipes-1" header="src/app/app.component.html" linenums="false"> <code-example path="template-expression-operators/src/app/app.component.html" region="uppercase-pipe" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
The pipe operator passes the result of an expression on the left to a pipe function on the right. 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: You can chain expressions through multiple pipes:
<code-example path="template-syntax/src/app/app.component.html" region="pipes-2" header="src/app/app.component.html" linenums="false"> <code-example path="template-expression-operators/src/app/app.component.html" region="pipe-chain" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
And you can also [apply parameters](guide/pipes#parameterizing-a-pipe) to a pipe: And you can also [apply parameters](guide/pipes#parameterizing-a-pipe) to a pipe:
<code-example path="template-syntax/src/app/app.component.html" region="pipes-3" header="src/app/app.component.html" linenums="false"> <code-example path="template-expression-operators/src/app/app.component.html" region="date-pipe" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
The `json` pipe is particularly helpful for debugging bindings: The `json` pipe is particularly helpful for debugging bindings:
<code-example path="template-syntax/src/app/app.component.html" linenums="false" header="src/app/app.component.html (pipes-json)" region="pipes-json"> <code-example path="template-expression-operators/src/app/app.component.html" region="json-pipe" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
The generated output would look something like this The generated output would look something like this:
<code-example language="json"> <code-example language="json">
{ "id": 0, "name": "Hercules", "emotion": "happy", { "name": "Telephone",
"birthdate": "1970-02-25T08:00:00.000Z", "manufactureDate": "1980-02-25T05:00:00.000Z",
"url": "http://www.imdb.com/title/tt0065832/", "price": 98 }
"rate": 325 }
</code-example> </code-example>
<div class="alert is-helpful">
**Note**: The pipe operator has a higher precedence than the ternary operator (`?:`),
which means `a ? b : c | x` is parsed as `a ? b : (c | x)`.
Nevertheless, for a number of reasons,
the pipe operator cannot be used without parentheses in the first and second operands of `?:`.
A good practice is to use parentheses in the third operand too.
</div>
<hr/> <hr/>
{@a safe-navigation-operator} {@a safe-navigation-operator}
### The safe navigation operator ( <span class="syntax">?.</span> ) and null property paths ### The safe navigation operator ( `?` ) and null property paths
The Angular **safe navigation operator (`?.`)** is a fluent and convenient way to The Angular safe navigation operator, `?`, guards against `null` and `undefined`
guard against null and undefined values in property paths. values in property paths. Here, it protects against a view render failure if `item` is `null`.
Here it is, protecting against a view render failure if the `currentHero` is null.
<code-example path="template-syntax/src/app/app.component.html" region="safe-2" header="src/app/app.component.html" linenums="false"> <code-example path="template-expression-operators/src/app/app.component.html" region="safe" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
What happens when the following data bound `title` property is null? If `item` is `null`, the view still renders but the displayed value is blank; you see only "The item name is:" with nothing after it.
<code-example path="template-syntax/src/app/app.component.html" region="safe-1" header="src/app/app.component.html" linenums="false"> Consider the next example, with a `nullItem`.
</code-example>
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 `name` of a null hero.
<code-example language="html"> <code-example language="html">
The null hero's name is {{nullHero.name}} The null item name is {{nullItem.name}}
</code-example> </code-example>
JavaScript throws a null reference error, and so does Angular: Since there is no safe navigation operator and `nullItem` is `null`, JavaScript and Angular would throw a `null` reference error and break the rendering process of Angular:
<code-example format="nocode"> <code-example format="nocode">
TypeError: Cannot read property 'name' of null in [null]. TypeError: Cannot read property 'name' of null.
</code-example> </code-example>
Worse, the *entire view disappears*. Sometimes however, `null` values in the property
path may be OK under certain circumstances,
especially when the value starts out null but the data arrives eventually.
This would be reasonable behavior if the `hero` property could never be null. With the safe navigation operator, `?`, Angular stops evaluating the expression when it hits the first `null` value and renders the view without errors.
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](guide/template-syntax#ngIf).
<code-example path="template-syntax/src/app/app.component.html" region="safe-4" header="src/app/app.component.html" linenums="false">
</code-example>
You could try to chain parts of the property path with `&&`, knowing that the expression bails out
when it encounters the first null.
<code-example path="template-syntax/src/app/app.component.html" region="safe-5" header="src/app/app.component.html" linenums="false">
</code-example>
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.
<code-example path="template-syntax/src/app/app.component.html" region="safe-6" header="src/app/app.component.html" linenums="false">
</code-example>
It works perfectly with long property paths such as `a?.b?.c?.d`. It works perfectly with long property paths such as `a?.b?.c?.d`.
@ -2242,34 +2216,31 @@ It works perfectly with long property paths such as `a?.b?.c?.d`.
{@a non-null-assertion-operator} {@a non-null-assertion-operator}
### The non-null assertion operator ( <span class="syntax">!</span> ) ### The non-null assertion operator ( `!` )
As of Typescript 2.0, you can enforce [strict null checking](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html "Strict null checking in TypeScript") with the `--strictNullChecks` flag. TypeScript then ensures that no variable is _unintentionally_ null or undefined. As of Typescript 2.0, you can enforce [strict null checking](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html "Strict null checking in TypeScript") with the `--strictNullChecks` flag. TypeScript then ensures that no variable is unintentionally null or undefined.
In this mode, typed variables disallow null and undefined by default. The type checker throws an error if you leave a variable unassigned or try to assign null or undefined to a variable whose type disallows null and undefined. In this mode, typed variables disallow `null` and `undefined` by default. The type checker throws an error if you leave a variable unassigned or try to assign `null` or `undefined` to a variable whose type disallows `null` and `undefined`.
The type checker also throws an error if it can't determine whether a variable will be null or undefined at runtime. The type checker also throws an error if it can't determine whether a variable will be `null` or undefined at runtime. You tell the type checker not to throw an error by applying the postfix
You may know that can't happen but the type checker doesn't know. [non-null assertion operator, !](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator "Non-null assertion operator").
You tell the type checker that it can't happen by applying the post-fix
[_non-null assertion operator (!)_](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator "Non-null assertion operator").
The _Angular_ **non-null assertion operator (`!`)** serves the same purpose in an Angular template. The Angular non-null assertion operator, `!`, serves the same purpose in
an Angular template. For example, after you use [*ngIf](guide/template-syntax#ngIf)
to check that `item` is defined, you can assert that
`item` properties are also defined.
For example, after you use [*ngIf](guide/template-syntax#ngIf) to check that `hero` is defined, you can assert that <code-example path="template-expression-operators/src/app/app.component.html" region="non-null" header="src/app/app.component.html" linenums="false">
`hero` properties are also defined.
<code-example path="template-syntax/src/app/app.component.html" region="non-null-assertion-1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
When the Angular compiler turns your template into TypeScript code, When the Angular compiler turns your template into TypeScript code,
it prevents TypeScript from reporting that `hero.name` might be null or undefined. it prevents TypeScript from reporting that `item` might be `null` or `undefined`.
Unlike the [_safe navigation operator_](guide/template-syntax#safe-navigation-operator "Safe navigation operator (?.)"), Unlike the [_safe navigation operator_](guide/template-syntax#safe-navigation-operator "Safe navigation operator (?)"),
the **non-null assertion operator** does not guard against null or undefined. the non-null assertion operator does not guard against `null` or `undefined`.
Rather it tells the TypeScript type checker to suspend strict null checks for a specific property expression. Rather, it tells the TypeScript type checker to suspend strict `null` checks for a specific property expression.
You'll need this template operator when you turn on strict null checks. It's optional otherwise.
The non-null assertion operator, `!`, is optional with the exception that you must use it when you turn on strict null checks.
<a href="#top-of-page">back to top</a> <a href="#top-of-page">back to top</a>