The complete source code for the example app in this chapter is
! in GitHub.
!
HTML
HTML is the language of the Angular template. Our QuickStart application had a template that was pure HTML:
<h3>My First Angular Application</h3>
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 simply ignored.)
--- 105,113 ----
!
The live example
! demonstrates all of the syntax and code snippets described in this chapter.
!
HTML
HTML is the language of the Angular template. Our QuickStart application had a template that was pure HTML:
<h3>My First Angular Application</h3>
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 simply ignored.)
***************
*** 146,171 ****
We put a template expression within the interpolation braces when we wrote {{1 + 1}}.
We’ll see template expressions again in the property binding section,
appearing in quotes to the right of the = symbol as in [property]="expression".
!
We write template expressions in a language that looks like Dart.
! Many Dart expressions are legal template expressions, but not all.
! Dart expressions that have or promote side effects are prohibited,
including:
!
assignments (=, +=, -=, ...)
!
creating instances using new or const
!
increment and decrement (++ and --)
!
Other notable differences from Dart syntax include:
-
no support for Dart string interpolation; for example,
- instead of "'The title is $title'", you must use
- "'The title is ' + title"
Perhaps more surprising, template expressions can’t refer to static properties, nor to top-level variables or functions, such as window or document from dart:html. They can’t directly call print or functions imported from dart:math. They are restricted to referencing members of the expression context.
The expression context is typically the component instance, which is
the source of binding values.
When we see title wrapped in double-curly braces, {{title}},
--- 146,169 ----
We put a template expression within the interpolation braces when we wrote {{1 + 1}}.
We’ll see template expressions again in the property binding section,
appearing in quotes to the right of the = symbol as in [property]="expression".
!
We write 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 (=, +=, -=)
!
the new operator
!
chaining expressions with ; or ,
!
increment and decrement operators (++ and --)
!
Other notable differences from JavaScript syntax include:
Perhaps more surprising, template expressions cannot refer to anything in the global namespace. They can’t refer to window or document. They can’t call console.log or Math.max. They are restricted to referencing members of the expression context.
The expression context is typically the component instance, which is
the source of binding values.
When we see title wrapped in double-curly braces, {{title}},
***************
*** 211,225 ****
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 a DateTime, Map, or List),
it returns the same object reference when called twice in a row.
!
Dart difference: Arrays are lists
Arrays in JavaScript correspond to lists in Dart
! (instances of the List class).
! This chapter uses array and list interchangeably.
! For more information, see
! Lists
! in the Dart language tour.
!
Template statements
A template statement responds to an event raised by a binding target
such as an element, component, or directive.
We’ll see template statements in the event binding section,
--- 209,217 ----
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 a Date or Array),
it returns the same object reference when called twice in a row.
!
Template statements
A template statement responds to an event raised by a binding target
such as an element, component, or directive.
We’ll see template statements in the event binding section,
***************
*** 229,249 ****
There would be no point to responding to an event otherwise.
Responding to events is the other side of Angular's "unidirectional data flow".
We're free to change anything, anywhere, during this turn of the event loop.
!
Like template expressions, template statements use a language that looks like Dart.
The template statement parser is different than the template expression parser and
! specifically supports both basic assignment (=) and chaining expressions with semicolons (;).
!
However, certain Dart syntax is not allowed:
!
the new and const keywords
increment and decrement operators, ++ and --
operator assignment, such as += and -=
!
the operators | and & (neither for bitwise operations
! nor for the Angular pipe operator)
Statement context
As with expressions, statements can refer only to what's in the statement context — typically the
component instance to which we're binding the event.
! Template statements can’t refer to static properties on the class, nor to top-level variables or functions, such as window or document from dart:html. They can’t directly call print or functions imported from dart:math.
The onSave in (click)="onSave()" is sure to be a method of the data-bound component instance.
The statement context may include an object other than the component.
A template reference variable is one such alternative context object.
--- 221,241 ----
There would be no point to responding to an event otherwise.
Responding to events is the other side of Angular's "unidirectional data flow".
We'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 semicolons (;) and commas (,).
!
However, certain JavaScript syntax is not allowed:
As with expressions, statements can refer only to what's in the statement context — typically the
component instance to which we're binding the event.
! Template statements cannot refer to anything in the global namespace. They can’t refer to window or document. They can’t call console.log or Math.max.
The onSave in (click)="onSave()" is sure to be a method of the data-bound component instance.
The statement context may include an object other than the component.
A template reference variable is one such alternative context object.
***************
*** 284,290 ****
In the normal course of HTML development, we create a visual structure with HTML elements, and
we modify those elements by setting element attributes with string constants.
We still create a structure and initialize attribute values this way in Angular templates.
Then we learn to create new elements with components that encapsulate HTML
--- 276,282 ----
In the normal course of HTML development, we create a visual structure with HTML elements, and
we modify those elements by setting element attributes with string constants.
We still create a structure and initialize attribute values this way in Angular templates.
Then we learn to create new elements with components that encapsulate HTML
***************
*** 346,352 ****
(element | component | directive) property, an
(element | component | directive) event, or (rarely) an attribute name.
The following table summarizes:
!
Binding type
Target
Examples
Property
Element property
Component property
Directive property
<img [src] = "heroImageUrl">
<hero-detail [hero]="currentHero"></hero-detail>
--- 338,344 ----
(element | component | directive) property, an
(element | component | directive) event, or (rarely) an attribute name.
The following table summarizes:
!
Binding type
Target
Examples
Property
Element property
Component property
Directive property
<button [attr.aria-label]="help">help</button>
***************
*** 387,393 ****
See the API reference for
viewChild and
contentChild.
!
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.
<img [src]="heroImageUrl">
Some people prefer the bind- prefix alternative, known as the canonical form:
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.
<img [src]="heroImageUrl">
Some people prefer the bind- prefix alternative, known as the canonical form:
***************
*** 418,432 ****
If we forget 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:
! <!-- BAD! HeroDetailComponent.hero expects a Hero object,
! not the string "currentHero".
!
! <hero-detail hero="currentHero"></hero-detail> -->
!
Dart difference: Type exceptions
In checked mode, uncommenting the <hero-detail> element above causes a type exception,
! because String isn't a subtype of Hero.
! For information on checked mode, see Important concepts
! in the Dart language tour.
!
One-time string initialization
We should omit the brackets when all of the following are true:
The target property accepts a string value.
--- 410,422 ----
If we forget 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:
! <!--
! BAD!
! HeroDetailComponent.hero expects a Hero object,
! not the string "currentHero"
! -->
! <hero-detail hero="currentHero"></hero-detail>
!
One-time string initialization
We should omit the brackets when all of the following are true:
The target property accepts a string value.
***************
*** 444,451 ****
Interpolated: <img src="{{heroImageUrl}}"><br>
Property bound: <img [src]="heroImageUrl">
! <div>The interpolated title is {{title}}</div>
! <div [textContent]="'The [textContent] title is '+title"></div>
Interpolation is a convenient alternative for property binding in many cases.
In fact, Angular translates those interpolations into the corresponding property bindings
before rendering the view.
--- 434,441 ----
Interpolated: <img src="{{heroImageUrl}}"><br>
Property bound: <img [src]="heroImageUrl">
! <div>The interpolated title is {{title}}</div>
! <div [innerHTML]="'The [innerHTML] title is '+title"></div>
Interpolation is a convenient alternative for property binding in many cases.
In fact, Angular translates those interpolations into the corresponding property bindings
before rendering the view.
***************
*** 512,522 ****
<!-- reset/override all class names with a binding -->
<div class="bad curly special"
[class]="badCurly">Bad curly</div>
! <p><b>Note:</b> "Bad curly" should be smaller but isn't, due to
! <a href="http://github.com/angular/angular/issues/6901">issue #6901</a>.</p>
!
Finally, we can bind to a specific class name.
! Angular adds the class when the template expression evaluates to true.
! It removes the class when the expression evaluates to false.
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
--- 502,510 ----
<!-- reset/override all class names with a binding -->
<div class="bad curly special"
[class]="badCurly">Bad curly</div>
!
Finally, we can bind to a specific class name.
! Angular adds the class when the template expression evaluates to something truthy.
! It removes the class when the expression is falsey.
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
***************
*** 530,540 ****
Style binding syntax resembles property binding.
Instead of an element property between brackets, we start with the prefix style,
followed by a dot (.) and the name of a CSS style property: [style.style-property].
While this is a fine way to set a single style,
we generally prefer the NgStyle directive when setting several inline styles at the same time.
Event binding
--- 518,528 ----
Style binding syntax resembles property binding.
Instead of an element property between brackets, we start with the prefix style,
followed by a dot (.) and the name of a CSS style property: [style.style-property].
While this is a fine way to set a single style,
we generally prefer the NgStyle directive when setting several inline styles at the same time.
Event binding
***************
*** 593,611 ****
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:
// This component make a request but it can't actually delete a hero.
! final deleteRequest = new EventEmitter<Hero>();
! void delete() {
! deleteRequest.emit(hero);
}
The component defines a deleteRequest property that returns an EventEmitter.
When the user clicks delete, the component invokes the delete() method
--- 581,599 ----
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:
// This component make a request but it can't actually delete a hero.
! deleteRequest = new EventEmitter<Hero>();
! delete() {
! this.deleteRequest.emit(this.hero);
}
The component defines a deleteRequest property that returns an EventEmitter.
When the user clicks delete, the component invokes the delete() method
***************
*** 621,627 ****
including queries and saves to a remote server.
These changes percolate through the system and are ultimately displayed in this and other views.
It's all good.
!
Two-way binding with NgModel
When developing data entry forms, we often want to both display a data property and update that property when the user makes changes.
The [(NgModel)] two-way data binding syntax makes that easy. Here's an example:
--- 609,640 ----
including queries and saves to a remote server.
These changes percolate through the system and are ultimately displayed in this and other views.
It's all good.
!
Two-way binding with NgModel
When developing data entry forms, we often want to both display a data property and update that property when the user makes changes.
The [(NgModel)] two-way data binding syntax makes that easy. Here's an example:
***************
*** 641,648 ****
Who wants to look that up each time?
That ngModel directive hides these onerous details behind its own ngModel input and ngModelChange output properties.
The ngModel input property sets the element's value property and the ngModelChange output 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 elements,
--- 654,661 ----
Who wants to look that up each time?
That ngModel directive hides these onerous details behind its own ngModel input and ngModelChange output properties.
The ngModel input property sets the element's value property and the ngModelChange output 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 elements,
***************
*** 664,671 ****
If we need to do something more or something different, we need to write the expanded form ourselves.
Let's try something silly like forcing the input value to uppercase:
Here are all variations in action, including the uppercase version:
Built-in directives
***************
*** 691,764 ****
when we want to add or remove many CSS classes at the same time.
A good way to apply NgClass is by binding it to a key:value control object. 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 component method such as setClasses that manages the state of three CSS classes:
Now we can add an NgClass property binding that calls setClasses
and sets the element's classes accordingly:
<div [ngClass]="setClasses()">This div is saveable and special</div>
NgStyle
We can set inline styles dynamically, based on the state of the component.
Binding to NgStyle lets us set many inline styles simultaneously.
A style binding is an easy way to set a single style value.
! <div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
! This div is x-large.
</div>
The NgStyle directive may be the better choice
when we want to set many inline styles at the same time.
We apply NgStyle by binding it to a key:value control object.
Each key of the object is a style name; its value is whatever is appropriate for that style.
Consider a component method such as setStyles that returns an object defining three styles:
Don't forget the asterisk (*) in front of ngIf.
For more information, see * and <template>.
!
Binding to a false expression removes the element subtree from the DOM.
! <!-- not displayed because nullHero is false.
`nullHero.firstName` never has a chance to fail -->
! <div *ngIf="nullHero != null">Hello, {{nullHero.firstName}}</div>
<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>
!
Dart difference: No truthy values
In checked mode, Dart expects Boolean values
! (those with type bool) to be either true or false.
! Even in production mode, the only value Dart treats as true is
! the value true; all other values are false.
! TypeScript and JavaScript, on the other hand, treat
! many values (including non-null objects) as true.
! A TypeScript Angular 2 program, for example, often has code like
! *ngIf="currentHero" where a Dart program has code like
! *ngIf="currentHero != null".
!
When converting TypeScript code to Dart code, watch out for
! true/false problems. For example, forgetting the != null
! can lead to exceptions in checked mode, such as
! "EXCEPTION: type 'Hero' is not a subtype of type 'bool' of 'boolean expression'".
! For more information, see
! Booleans
! in the Dart language tour.
!
Visibility and NgIf are not the same
We can show and hide an element subtree (the element and its children) with a
class or style binding:
<!-- isSpecial is true -->
--- 704,759 ----
when we want to add or remove many CSS classes at the same time.
A good way to apply NgClass is by binding it to a key:value control object. 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 component method such as setClasses that manages the state of three CSS classes:
Now we can add an NgClass property binding that calls setClasses
and sets the element's classes accordingly:
<div [ngClass]="setClasses()">This div is saveable and special</div>
NgStyle
We can set inline styles dynamically, based on the state of the component.
Binding to NgStyle lets us set many inline styles simultaneously.
A style binding is an easy way to set a single style value.
! <div [style.fontSize]="isSpecial ? 'x-large' : 'smaller'" >
! This div is x-large
</div>
The NgStyle directive may be the better choice
when we want to set many inline styles at the same time.
We apply NgStyle by binding it to a key:value control object.
Each key of the object is a style name; its value is whatever is appropriate for that style.
Consider a component method such as setStyles that returns an object defining three styles:
Don't forget the asterisk (*) in front of ngIf.
For more information, see * and <template>.
!
Binding to a falsey expression removes the element subtree from the DOM.
! <!-- not displayed because nullHero is falsey.
`nullHero.firstName` never has a chance to fail -->
! <div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>
<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>
!
Visibility and NgIf are not the same
We can show and hide an element subtree (the element and its children) with a
class or style binding:
<!-- isSpecial is true -->
***************
*** 857,863 ****
It has no choice but to tear down the old list, discard those DOM elements, and re-build a new list with new DOM elements.
Angular can avoid this churn if we give it a tracking function that tells it what we know:
that two objects with the same hero.id are the same hero. Here is such a function:
! int trackByHeroes(int index, Hero hero) => hero.id;
Now set the NgForTrackBy directive to that tracking function.
Angular offers a variety of equivalent syntax choices including these two:
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
--- 852,858 ----
It has no choice but to tear down the old list, discard those DOM elements, and re-build a new list with new DOM elements.
Angular can avoid this churn if we give it a tracking function that tells it what we know:
that two objects with the same hero.id are the same hero. Here is such a function:
Now set the NgForTrackBy directive to that tracking function.
Angular offers a variety of equivalent syntax choices including these two:
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
***************
*** 879,919 ****
Angular strips away the * and expands the HTML into the <template> tags for us.
Expanding *ngIf
We can do what Angular does ourselves and expand the * prefix syntax to template syntax. Here's some code with *ngIf:
Notice that the [hero]="currentHero" binding remains on the child <hero-detail>
element inside the template.
!
Remember the brackets!
Don’t make the mistake of writing ngIf="currentHero"!
! That syntax assigns the string value "currentHero" to ngIf,
! which won't work because ngIf expects a bool.
Expanding *ngSwitch
A similar transformation applies to *ngSwitch. We can de-sugar the syntax ourselves.
Here's an example, first with *ngSwitchWhen and *ngSwitchDefault and then again with <template> tags:
The *ngSwitchWhen and *ngSwitchDefault expand in exactly the same manner as *ngIf,
wrapping their former elements in <template> tags.
Now we can see why the ngSwitch itself is not prefixed with an asterisk (*).
--- 874,916 ----
Angular strips away the * and expands the HTML into the <template> tags for us.
Expanding *ngIf
We can do what Angular does ourselves and expand the * prefix syntax to template syntax. Here's some code with *ngIf:
Notice that the [hero]="currentHero" binding remains on the child <hero-detail>
element inside the template.
!
Remember the brackets!
Don’t make the mistake of writing ngIf="currentHero"!
! That syntax assigns the string value "currentHero" to ngIf.
! In JavaScript a non-empty string is a truthy value, so ngIf would always be
! true and Angular would always display the hero-detail
! … even when there is no currentHero!
Expanding *ngSwitch
A similar transformation applies to *ngSwitch. We can de-sugar the syntax ourselves.
Here's an example, first with *ngSwitchWhen and *ngSwitchDefault and then again with <template> tags:
The *ngSwitchWhen and *ngSwitchDefault expand in exactly the same manner as *ngIf,
wrapping their former elements in <template> tags.
Now we can see why the ngSwitch itself is not prefixed with an asterisk (*).
***************
*** 929,935 ****
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
And here it is expanded further into a <template> tag wrapping the original <hero-detail> element:
The NgFor code is a bit more complex than NgIf because a repeater has more moving parts to configure.
In this case, we have to remember to create and assign the NgForOf directive that identifies the list and the NgForTrackBy directive.
--- 926,932 ----
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
And here it is expanded further into a <template> tag wrapping the original <hero-detail> element:
The NgFor code is a bit more complex than NgIf because a repeater has more moving parts to configure.
In this case, we have to remember to create and assign the NgForOf directive that identifies the list and the NgForTrackBy directive.
***************
*** 965,971 ****
<div class="form-group">
<label for="name">Name</label>
<input class="form-control" required ngControl="firstName"
! [(ngModel)]="currentHero.firstName">
</div>
<button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>
--- 962,968 ----
<div class="form-group">
<label for="name">Name</label>
<input class="form-control" required ngControl="firstName"
! [(ngModel)]="currentHero.firstName">
</div>
<button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>
***************
*** 1014,1028 ****
Target properties must be explicitly marked as inputs or outputs.
When we peek inside HeroDetailComponent, we see that these properties are marked
with decorators as input and output properties.
! @Input() Hero hero;
! @Output() final deleteRequest = new EventEmitter<Hero>();
Alternatively, we can identify members in the inputs and outputs arrays
of the directive metadata, as in this example:
We can specify an input/output property either with a decorator or in a metadata array.
Don't do both!
Input or output?
***************
*** 1048,1068 ****
In the example immediately above, we are actually binding through themyClickalias to
the directive's own clicks property.
We can specify the alias for the property name by passing it into the input/output decorator like this:
! // @Output(alias) [type info] propertyName = ...
! @Output('myClick') final EventEmitter clicks = new EventEmitter<String>();
We can also alias property names in the inputs and outputs arrays.
We write a colon-delimited (:) string with
the directive property name on the left and the public alias on the right:
The template expression language employs a subset of Dart syntax supplemented with a few special operators
for specific scenarios. We'll cover two of these operators: pipe and safe navigation operator.
!
Dart difference: ?. is a Dart operator
The safe navigation operator (?.) is part of the Dart language.
! It's considered a template expression operator because
! Angular 2 supports ?. even in TypeScript and JavaScript apps.
!
The pipe operator ( | )
The result of an expression might require some transformation before we’re ready to use it in a binding. For example, we might want to display a number as a currency, force text to uppercase, or filter a list and sort it.
Angular pipes are a good choice for small transformations such as these.
--- 1044,1060 ----
In the example immediately above, we are actually binding through themyClickalias to
the directive's own clicks property.
We can specify the alias for the property name by passing it into the input/output decorator like this:
We can also alias property names in the inputs and outputs arrays.
We write a colon-delimited (:) string with
the directive property name on the left and the public alias on the right:
The template expression language employs a subset of JavaScript syntax supplemented with a few special operators
for specific scenarios. We'll cover two of these operators: pipe and safe navigation operator.
!
The pipe operator ( | )
The result of an expression might require some transformation before we’re ready to use it in a binding. For example, we might want to display a number as a currency, force text to uppercase, or filter a list and sort it.
Angular pipes are a good choice for small transformations such as these.
***************
*** 1077,1085 ****
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 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.
***************
*** 1091,1098 ****
That is reasonable behavior. At least the app doesn't crash.
Suppose the template expression involves a property path, as in this next example
where we’re displaying the firstName of a null hero.
! The null hero's name is {{nullHero.firstName}}
Dart throws an exception, and so does Angular:
! EXCEPTION: The null object does not have a getter 'firstName'.
Worse, the entire view disappears.
We could claim that this is reasonable behavior if we believed that the hero property must never be null.
If it must never be null and yet it is null,
we've made a programming error that should be caught and fixed.
--- 1090,1097 ----
That is reasonable behavior. At least the app doesn't crash.
Suppose the template expression involves a property path, as in this next example
where we’re displaying the firstName of a null hero.
! The null hero's name is {{nullHero.firstName}}
JavaScript throws a null reference error, and so does Angular:
! TypeError: Cannot read property 'firstName' of null in [null]
Worse, the entire view disappears.
We could claim that this is reasonable behavior if we believed that the hero property must never be null.
If it must never be null and yet it is null,
we've made a programming error that should be caught and fixed.
***************
*** 1104,1113 ****
Unfortunately, our app crashes when the currentHero is null.
<!--No hero, div not displayed, no error -->
! <div *ngIf="nullHero != null">The null hero's name is {{nullHero.firstName}}</div>
!
This approach has 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.
--- 1103,1113 ----
Unfortunately, our app crashes when the currentHero is null.
<!--No hero, div not displayed, no error -->
! <div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
!
Or we could try to chain parts of the property path with &&, knowing that the expression bails out
! when it encounters the first null.
! The null hero's name is {{nullHero && nullHero.firstName}}
!
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.
***************
*** 1116,1122 ****
The null hero's name is {{nullHero?.firstName}}
It works perfectly with long property paths such as a?.b?.c?.d.
Summary
!
We’ve completed our survey of template syntax. Now it's time to put that knowledge to work as we write our own components and directives.