Empower your HTML
Angular brings powerful syntax to templates. In the previous chapter, we've seen text interpolation {{}}
. In this chapter, we will tackle some elements of this syntax system: property binding, event binding, class and style binding, attribute directives and structural directives.
Property binding
To bind an HTML element to a component's property, enclose it in square brackets []
. The brackets, []
, cause Angular to evaluate the right-hand side of the assignment as a dynamic expression. Without the brackets, Angular treats the right-hand side as a string literal and sets the property to that static value. []
is the syntax for one-way data binding with data flowing from the component to the template.
<a [href]="url">Link</a>
<button [disabled]="isUnchanged">Disabled Button</button>
Exercise: try to link the src
and width
attributes of the image
Class and style binding
Class binding
You can use class binding to add and remove CSS class names from an element's class
attribute. To create a single class binding, use the prefix class
followed by a dot and the name of the CSS class, for example, [class.sale]="onSale"
. Angular adds the class when the bound expression, onSale
, is truthy and it removes the class when the expression is falsy.
<p [class.my-class-1]="isWarning"></p>
Multiple classes can also be bound with the [class]
syntax:
<!-- classExpression = "my-class-1 my-class-2 my-class-3" -->
<!-- classExpression = {my-class-1: true, my-class-2: false} -->
<!-- classExpression = ['my-class-1', 'my-class-2'] -->
<p [class]="classExpression"></p>
Style binding
You can use style binding to set styles dynamically. To create a single style binding, use the prefix style
followed by a dot and the name of the CSS style property, for example, [style.width]="width" with width = "100px"
(width is a string). Optionally, you can add a unit extension like em
or %
: [style.width.px]="width" with width = 100
(width is a number).
<!-- Style properties can be written in dash-case or camelCase -->
<nav [style.background-color]="expression"></nav>
<nav [style.backgroundColor]="expression"></nav>
To toggle multiple styles, bind to the [style]
attribute:
<!-- styleExpression = "width: 100px; height: 100px; background-color: red;" -->
<!-- styleExpression = {width: '100px', height: '100px', backgroundColor: 'red'} -->
<p [style]="styleExpression"></p>
NgClass and NgStyle directive
Alternatively, you can toggle several styles or several classes in one go via two directives NgStyle et NgClass:
<!-- toggle the "special" class on/off with a property -->
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
<!-- toggle the "saveable" class on/off depending on canSave -->
<!-- toggle the "modified" class on/off depending on isUnchanged -->
<!-- toggle the "special" class on/off depending on isSpecial -->
<div [ngClass]="{saveable: canSave, modified: !isUnchanged, special: isSpecial}">
NgClass test div
</div>
<div [ngStyle]="{
'font-style': canSave ? 'italic' : 'normal',
'font-weight': !isUnchanged ? 'bold' : 'normal',
'font-size': isSpecial ? '24px' : '12px'
}">
NgStyle test div
</div>
Exercise: assign a class and a color to each ghost using [class], [style], [ngClass] and [ngStyle]
NgModel directive
The NgModel directive allows you to bind the value of a form field to a component data item. It is a two-way binding: the variable is updated when the content of the field changes (typically by the user) and vice versa. The syntax for two-way data binding is [()]
.
<label>
What is your name ?
<input [(ngModel)]="name">
</label>
<p>Hello {{ name }} !</p>
Test it yourself:
Hello !
Import
The NgModel
directive is not part of the default imports of an NgModule
. You have to add it yourself: add FormsModule
to the AppModule
's imports list.
Exercise: use [(ngModel)] on input, select, radio and checkbox tags
NgIf directive
You can add or remove an element by applying an NgIf
directive to a host element. When NgIf
is false, Angular removes an element and its descendants from the DOM. Angular then disposes of their class instances if there are any, which frees up memory and resources. If you only want to hide the element you can use [hidden]
which only adds/removes the display:none
CSS property on the element. NgIf
is helpful in providing a way to guard against null or undefined values.
<!--Will only show Hello, ... if currentCustomer is not null or undefined-->
<div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>
WARNING
Be careful when using NgIf
to test nullability on numeric values as 0
is a falsy value.
You can provide an else statement as follows:
<div *ngIf="condition; else elseBlock">Content to render when condition is true.</div>
<ng-template #elseBlock>Content to render when condition is false.</ng-template>
<ng-template>
creates a template fragment, it is not rendered by default. #elseBlock
is a template variable that enables to gain a reference to the <ng-template>
.
Exercise: use an *ngIf
to toggle the loader
Bonus: use an *ngIf else
to conditionnally show either the data or the no data message
NgSwitch directive
NgSwitch
conditionally swaps the content of the host element by selecting one of the embedded templates based on the current value of condition expression.
<div [ngSwitch]="myBeer">
<div *ngSwitchCase="'Ale'">Short fermentation</div>
<div *ngSwitchCase="'Lager'">Long fermentation</div>
<div *ngSwitchCase="'Sour ale'">Crafted from wild yeasts</div>
<div *ngSwitchDefault>No random knowledge for that type of beer, sorry.</div>
</div>
Exercise: use *ngSwitch
to alternate plant growth stage according to the season's temperature
NgFor directive
You can use the NgFor directive to present a list of items. The element on which NgFor
is placed will be repeated for each element in the iterable.
<div *ngFor="let item of items">{{item.name}}</div>
<!-- With a local variable for the index -->
<div *ngFor="let item of items; let i = index">{{i}}: {{item.name}}</div>
<!-- With a local variable to know whether it is an even item -->
<div *ngFor="let item of items; let isEven = even">
{{item.name}} is {{isEven ? 'even': 'odd'}}
</div>
The following exported values are also available to be aliased to local variables: count
, first
, last
, odd
.
*ngIf
and *ngFor
cannot be placed at the same time on an HTML element. To repeat a block of HTML when a particular condition is true, either an extra level of HTML needs to be introduced which isn't always desirable and can break the styling or the <ng-container>
tag provided by Angular can be used. <ng-container>
is not present in the DOM.
<!-- Without ng-container -->
<div *ngIf="condition">
<div *ngFor="let item of items">{{item.name}}</div>
</div>
<!-- With ng-container -->
<ng-container *ngIf="condition">
<div *ngFor="let item of items">{{item.name}}</div>
</ng-container>
Exercise: use two *ngFor
loops to display all the contents of the basket (one loop for each type of item, and within that loop another loop to print as many emoji of that item as its quantity)
Bonus: An intruder is in the fruit basket, hide the corn with an *ngIf
Event binding
Event binding allows you to listen for and respond to user actions such as keystrokes, mouse movements, clicks, and touches or a custom event emitted by a child component. To bind to an event you use the Angular event binding syntax ()
.
<button (click)="delete()">Delete</button>
Exercise: use events to add a monkey when clicking the button, and make them open their eyes on mouse hover
About directives
In this chapter, we have seen 5 built-in directives. Directives are classes declared with the @Directive
decorator.
There are three types of directives:
- Components which are directives with a template (
@Component
inherits from@Directive
) - Attribute directives which change the appearance or behavior of an element
- Structural directives which change the DOM layout by adding and removing DOM elements
Quizz: Which built-in directives are attribute directives and which are structural directives?
You can find more about building your own directives here and here.
Practical work: Film list
- In the LoginFormComponent, add two fields email (
email = ''
) and password (password = ''
) in the and use the[(ngModel)]
directive on the email and password inputs to bind them. Remember the warning in the NgModel section of the chapter: don't forget to import theFormsModule
in the module to use the NgModel directive. - Add another
loggedIn
field initially set tofalse
, then use event binding with(ngSubmit)
on the form tag to set it totrue
when the form is submitted (create a methodlogin()
in the component's class for that). - In
login-form.component.html
, add the following HTML under the authentication form :
<ul class="films">
<li class="film card">
<img
class="poster"
src="https://m.media-amazon.com/images/M/MV5BMDdmZGU3NDQtY2E5My00ZTliLWIzOTUtMTY4ZGI1YjdiNjk3XkEyXkFqcGdeQXVyNTA4NzY1MzY@._V1_SX300.jpg"
/>
<div>
<p class="title">
Titanic
<span class="rating">★★★★</span>
</p>
<dl>
<dt>Release date</dt>
<dd>07/01/1998</dd>
<dt>Director</dt>
<dd>James Cameron</dd>
<dt>Actors</dt>
<dd>Leonardo DiCaprio, Kate Winslet, Billy Zane, Kathy Bates</dd>
</dl>
<p class="plot">
84 years later, a 100 year-old woman named Rose DeWitt Bukater tells the
story to her granddaughter Lizzy Calvert, Brock Lovett, Lewis Bodine,
Bobby Buell and Anatoly Mikailavich on the Keldysh about her life set in
April 10th 1912, on a ship called Titanic when young Rose boards the
departing ship with the upper-class passengers and her mother, Ruth DeWitt
Bukater, and her fiancé, Caledon Hockley. Meanwhile, a drifter and artist
named Jack Dawson and his best friend Fabrizio De Rossi win third-class
tickets to the ship in a game. And she explains the whole story from
departure until the death of Titanic on its first and last voyage April
15th, 1912 at 2:20 in the morning.
</p>
</div>
</li>
</ul>
- Use the
*ngIf
directive andelse
template binding to display the authentication form and hide the films list whenloggedIn === false
, and vice versa. - Add the following model in the src/app/models folder, name the file film.ts:
export interface Film {
title: string
released: string
director: string
actors: string
poster: string
plot: string
metascore: string
}
- Add the following field in the class of the LoginFormComponent (you should get an error and be offered a fix suggestion to import the Film model in the component, you will see the @models alias in action):
films: Film[] = [
{
title: 'Titanic',
released: '19 Dec 1997',
director: 'James Cameron',
actors: 'Leonardo DiCaprio, Kate Winslet, Billy Zane, Kathy Bates',
poster: 'https://m.media-amazon.com/images/M/MV5BMDdmZGU3NDQtY2E5My00ZTliLWIzOTUtMTY4ZGI1YjdiNjk3XkEyXkFqcGdeQXVyNTA4NzY1MzY@._V1_SX300.jpg',
plot: `84 years later, a 100 year-old woman named Rose DeWitt Bukater tells the story to her granddaughter Lizzy Calvert, Brock Lovett, Lewis Bodine, Bobby Buell and Anatoly Mikailavich on the Keldysh about
her life set in April 10th 1912, on a ship called Titanic when young Rose boards the departing ship with the upper-class passengers and her mother, Ruth DeWitt Bukater, and her fiancé, Caledon Hockley.
Meanwhile, a drifter and artist named Jack Dawson and his best friend Fabrizio De Rossi win third-class tickets to the ship in a game. And she explains the whole story from departure until the death of Titanic
on its first and last voyage April 15th, 1912 at 2:20 in the morning.`,
metascore: '75'
},
{
title: 'Blade Runner',
released: '25 Jun 1982',
director: 'Ridley Scott',
actors: 'Harrison Ford, Rutger Hauer, Sean Young, Edward James Olmos',
poster: 'https://m.media-amazon.com/images/M/MV5BNzQzMzJhZTEtOWM4NS00MTdhLTg0YjgtMjM4MDRkZjUwZDBlXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg',
plot: 'A blade runner must pursue and terminate four replicants who stole a ship in space, and have returned to Earth to find their creator.',
metascore: '89'
},
{
title: 'The Shining',
released: '13 Jun 1980',
director: 'Stanley Kubrick',
actors: 'Jack Nicholson, Shelley Duvall, Danny Lloyd, Scatman Crothers',
poster: 'https://m.media-amazon.com/images/M/MV5BZWFlYmY2MGEtZjVkYS00YzU4LTg0YjQtYzY1ZGE3NTA5NGQxXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg',
plot: 'A family heads to an isolated hotel for the winter where an evil spiritual presence influences the father into violence, while his psychic son sees horrific forebodings from both past and future.',
metascore: '63'
}
]
- Using the
*ngFor
directive, repeat the.film.card
element to display as many films as there is in thefilms
list. At this stage you are seeing Titanic three times, let's take care of this in the next step. - Complete the card with data from each film using property binding and interpolation.
- Bonus: Use the
metascore
property to display a number of stars (from 1 to 5 ★) next to each film title. (Create a methodstarRating
returning a string containing the right number of stars) - Bonus: Use an
ng-container
tag to only display movies with a metacritic score above 70 (moving forward we won't need this change so revert it once you have managed to make it work). - Don't forget to commit.