diff --git a/aio/content/examples/template-expression-operators/e2e/src/app.e2e-spec.ts b/aio/content/examples/template-expression-operators/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..bb5e8f4501 --- /dev/null +++ b/aio/content/examples/template-expression-operators/e2e/src/app.e2e-spec.ts @@ -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'); + }); + + +}); diff --git a/aio/content/examples/template-expression-operators/example-config.json b/aio/content/examples/template-expression-operators/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/template-expression-operators/src/app/app.component.css b/aio/content/examples/template-expression-operators/src/app/app.component.css new file mode 100644 index 0000000000..3e58f2fe73 --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/app/app.component.css @@ -0,0 +1,10 @@ +.no-show-div { + background-color: #ecfdff; + border: 1px #444 solid; + padding: 1rem; + color: #444; +} + +.error { + color: red; +} diff --git a/aio/content/examples/template-expression-operators/src/app/app.component.html b/aio/content/examples/template-expression-operators/src/app/app.component.html new file mode 100644 index 0000000000..b2e04ec7d2 --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/app/app.component.html @@ -0,0 +1,80 @@ +

{{title}}

+ +
+ +

Pipes

+ +

Title through uppercase pipe: {{title | uppercase}}

+ + + +

Title through a pipe chain: {{title | uppercase | lowercase}}

+ + + + +

Manufacture date with date format pipe: {{item.manufactureDate | date:'longDate'}}

+ +

Manufacture date with uppercase pipe: {{(item.manufactureDate | date:'longDate') | uppercase}}

+ +

Item json pipe: {{item | json}}

+ + + +

Price with currency pipe: {{item.price | currency:'USD'}}

+ +
+ +

Safe navigation operator: ? and null

+ +

The item name is: {{item?.name}}

+ + + +

The nullItem name is: {{nullItem?.name}}

+ + +
+ +

Error because nullItem is null:

+ + + +
+

Uncomment above paragraph and see console log for error about nullItem.name:

+

TypeError: Cannot read property 'name' of null

+
+ +
+ +

The div will not display and there's no error:

+
+

There's a child paragraph element in here that doesn't show.

+

The null item's name {{nullItem.name}}

+
+
+ +

Div shows but interpolation doesn't:

+
+

Using and (&&)

+

The null item's name is: {{nullItem && nullItem.name}}

+
+ +
+

Using safe navigation operator (?)

+ +

The null item's name is: {{nullItem?.name}}

+
+ + + +
+

Non-null assertion operator (!)

+
+ + +

The item's color is: {{item!.color}}

+ + + + diff --git a/aio/content/examples/template-expression-operators/src/app/app.component.spec.ts b/aio/content/examples/template-expression-operators/src/app/app.component.spec.ts new file mode 100644 index 0000000000..bcbdf36b3e --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/app/app.component.spec.ts @@ -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!'); + })); +}); diff --git a/aio/content/examples/template-expression-operators/src/app/app.component.ts b/aio/content/examples/template-expression-operators/src/app/app.component.ts new file mode 100644 index 0000000000..e0a6ddf2f5 --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/app/app.component.ts @@ -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; + +} + diff --git a/aio/content/examples/template-expression-operators/src/app/app.module.ts b/aio/content/examples/template-expression-operators/src/app/app.module.ts new file mode 100644 index 0000000000..926975afe8 --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/app/app.module.ts @@ -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 { } diff --git a/aio/content/examples/template-expression-operators/src/index.html b/aio/content/examples/template-expression-operators/src/index.html new file mode 100644 index 0000000000..30c82e152b --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/index.html @@ -0,0 +1,14 @@ + + + + + Template Expression Operators Example + + + + + + + Loading... + + diff --git a/aio/content/examples/template-expression-operators/src/main.ts b/aio/content/examples/template-expression-operators/src/main.ts new file mode 100644 index 0000000000..a9ca1caf8c --- /dev/null +++ b/aio/content/examples/template-expression-operators/src/main.ts @@ -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); diff --git a/aio/content/examples/template-expression-operators/stackblitz.json b/aio/content/examples/template-expression-operators/stackblitz.json new file mode 100644 index 0000000000..9b21040545 --- /dev/null +++ b/aio/content/examples/template-expression-operators/stackblitz.json @@ -0,0 +1,10 @@ +{ + "description": "Template Expression Operators", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "file": "src/app/app.component.ts", + "tags": ["Template Expression Operators"] +} diff --git a/aio/content/guide/template-syntax.md b/aio/content/guide/template-syntax.md index 487912e4ec..8165cf751c 100644 --- a/aio/content/guide/template-syntax.md +++ b/aio/content/guide/template-syntax.md @@ -2121,119 +2121,93 @@ You can specify the alias for the property name by passing the alias name to the ## 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_. +The Angular template expression language employs a subset of JavaScript syntax supplemented with a few special operators +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} -### The pipe operator ( | ) +### The pipe operator (`|`) The result of an expression might require some transformation before you're ready to use it in a binding. -For example, you might display a number as a currency, force text to uppercase, or filter a list and sort it. +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. -They're easy to apply within template expressions, using the **pipe operator (`|`)**: +They're easy to apply within template expressions, using the pipe operator (`|`): - + 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: - + And you can also [apply parameters](guide/pipes#parameterizing-a-pipe) to a pipe: - + The `json` pipe is particularly helpful for debugging bindings: - + -The generated output would look something like this +The generated output would look something like this: - { "id": 0, "name": "Hercules", "emotion": "happy", - "birthdate": "1970-02-25T08:00:00.000Z", - "url": "http://www.imdb.com/title/tt0065832/", - "rate": 325 } + { "name": "Telephone", + "manufactureDate": "1980-02-25T05:00:00.000Z", + "price": 98 } +
+ +**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. + +
+
{@a safe-navigation-operator} -### The safe navigation operator ( ?. ) and null property paths +### The safe navigation operator ( `?` ) 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. +The Angular safe navigation operator, `?`, guards against `null` and `undefined` +values in property paths. Here, it protects against a view render failure if `item` is `null`. - + -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. - - - -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. +Consider the next example, with a `nullItem`. - The null hero's name is {{nullHero.name}} + The null item name is {{nullItem.name}} -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: - TypeError: Cannot read property 'name' of null in [null]. + TypeError: Cannot read property 'name' of null. -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. -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). - - - - -You could try to chain parts of the property path with `&&`, knowing that the expression bails out -when it encounters the first null. - - - - -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. - - - +With the safe navigation operator, `?`, Angular stops evaluating the expression when it hits the first `null` value and renders the view without errors. 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} -### The non-null assertion operator ( ! ) +### 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. -You may know that can't happen but the type checker doesn't know. -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 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 +[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 -`hero` properties are also defined. - - + 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 (?.)"), -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. - -You'll need this template operator when you turn on strict null checks. It's optional otherwise. +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`. +Rather, it tells the TypeScript type checker to suspend strict `null` checks for a specific property expression. +The non-null assertion operator, `!`, is optional with the exception that you must use it when you turn on strict null checks. back to top