4. Template Syntax
Extend HTML vocabulary of our templates with components and directives that appear as new elements and attributes.
Interpolation, double curly braces:
<h2>My favorite technology is: {{ myTechnology.name }}</h2>
<h1>{{ title }}</h1>
<img src="{{ imageUrl }}" style="width:60rem; border-radius:50%">
<p>The sum of 2 + 2 + 2 is {{ 2 + 2 + 2 }}</p>
<p>The sum of 2 + 2 + 2 is not {{ 2 + 2 + getValue() }}</p>
Template Expression produces a value. Expression context is typically component instance.
title in double curly braces and isUnchanged in double quotes refer to properties of AppComponent:
{{ title }}
<span [hidden]="isUnchanged">changed</span>
Expression can also refer to properties of templates context such as template input variable let technology, or template reference variable #technologyInput:
<li *ngFor="let technology of technologies">
{{ technology.name }}
</li>
<input #technologyInput> {{ technologyInput.value }} //wrap me in a (keyup)="0"
Template Statement has a side effect, appears to the right of the = symbol:
<button (click)="delete(technology)">x</button>
Data Binding:
One way from component to view. Interpolation, property, attribute, class, style:
{{ expression }}
[target]="expression"
bind-target="expression
One way from view to component. Event:
(target)="statement
on-target="statement"
Two way:
[(target)]="expression"
bindon-target="expression"
Property Binding:
<img [src]="imageUrl">
<button [disabled]="isDisabled">Button is disabled</button>
<div [ngClass]="red">[ngClass] binding to red, color of passion and love, property</div>
<app-technology-detail [technology]="technology"></app-technology-detail>
One-time string initialization:
<app-technology-detail prefix="You are my" [technology]="technology"></app-technology-detail>
Property Binding and Interpolation:
<p><img [src]="imageUrl" style="width:30rem; border-radius:50%"></p>
<p><img src={{imageUrl}} style="width:30rem; border-radius:50%"></p>
<p><span>{{ title }}</span></p>
<p><span [innerHTML]="title"></span></p>
Content Security:
Angular sanitizes HTML. script tags are not allowed to leak into the browser:
evilScript = 'Template <script>alert("oneiro who deceive men with false visions, arrive through a gate of ivory")</script>Syntax';
<p><span>{{ evilScript }}</span></p>
<p><span [innerHTML]="evilScript"></span></p>
Attribute Binding:
<table>
<tr>
<td [attr.colspan]="1 + 1">1-2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</table>
<button [attr.aria-label]="actionName">{{ actionName }} with Aria</button>
Class Binding:
<div class="red" [class]="redy">The color of passion, desire and love.</div> // <-- all or nothing
<div [class.special]="isSpecial">This class binding is special.</div>
<div class="redy" [class.special]="!isSpecial">This class binding is not so special.</div> // <-- binding class.special trumps class attribute
Style Binding:
<button [style.color]="isSpecial ? 'red' : 'green'">I love you</button>
<button [style.background-color]="canSaveMyPrincess ? 'yellow' : 'cyan'">I Save Her</button>
<button [style.font-size.em]="isSpecial ? 5 : 1">Big</button>
<button [style.fontSize.%]="!isSpecial ? 100: 50">Small</button>
Event Binding:
Template:
<button class="btn btn-danger" (click)="onSave()">Save</button>
<button class="btn btn-success" on-click="onSave()">On Save</button>
<div class="btn btn-warning" (myClick)="clickMessage=$event" clickable>Click with myClick</div>
{{ clickMessage }}
Component:
clickMesssage = '';
onSave() {
console.log('save clicked');
}
Directive:
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
@Directive({selector: '[myClick]'})
export class ClickDirective {
@Output('myClick') clicks = new EventEmitter<string>();
toggle = false;
constructor(el: ElementRef) {
el.nativeElement.addEventListener('click', (event: Event) => {
this.toggle = !this.toggle;
this.clicks.emit(this.toggle ? 'Click!' : '');
});
}
}
$event and event handling statements:
<input [value]="myTechnology" (input)="myTechnology=$event.target.value">
Custom Events with EventEmitter:
TechnologyDetailComponent:
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-technology-detail',
template: `
<span>
{{ technology }}
</span>
<button class="btn btn-danger" (click)="delete(technology)">Delete</button>
`
})
export class TechnologyDetailComponent {
@Input() technology;
@Output() deleteRequest = new EventEmitter<string>();
delete(technology: string) {
this.deleteRequest.emit(technology);
}
}
AppComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<ul>
<li *ngFor="let technology of technologies">
<app-technology-detail (deleteRequest)="delete($event)" [technology]="technology">
</app-technology-detail>
</li>
</ul>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Angular 4 Love Affair';
technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
delete(value) {
const index = this.technologies.indexOf(value);
this.technologies.splice(index, 1);
}
}
Two-way Binding:
SizerComponent:
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-sizer',
template: `
<div>
<button class="btn btn-success" (click)="decrement()" title="smaller">--</button>
<button class="btn btn-danger" (click)="increment()" title="bigger">++</button>
<label [style.font-size.px]="size">FontSize: {{ size }}px</label>
</div>
`
})
export class SizerComponent {
@Input() size: number | string;
@Output() sizeChange = new EventEmitter<number>();
decrement() { this.resize(-1); }
increment() { this.resize(+1); }
resize(delta: number) {
this.size = Math.min(40, Math.max(8, +this.size + delta));
this.sizeChange.emit(this.size);
}
}
AppComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Angular 4 Love Affair';
technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
fontSizePx = 20;
}
Desugar:
<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>
Attribute Directives:
- NgClass: add and remove a set of CSS classes.
- NgStyle: add and remove a set of CSS styles.
- NgModel: two-way data binding to an HTML from element.
NgClass:
<div [class.special]="isSpecial">My Chromebook is special</div>
<div [ngClass]="{special: 'isSpecial'}">My Chromebook is special</div>
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<div [ngClass]="currentClasses">This div is initially saveable, unchanged and special.</div>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`,
styles: [`.saveable { color: red; } .modified { border-style: solid; } .special { font-weight: bold; font-size: x-large; }`]
})
export class AppComponent {
title = 'Angular 4 Love Affair';
technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
private canSave = true;
private isUnchanged = false;
private isSpecial = true;
currentClasses: {};
constructor() {
this.setCurrentClasses();
}
setCurrentClasses() {
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
}
NgStyle:
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
This div is x-large.
</div>
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<div [ngStyle]="setCurrentStyles()">
This div is initially italic, normal weight, and extra large (24px).
</div>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Angular 4 Love Affair';
technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
private canSave = true;
private isUnchanged = true;
isSpecial = true;
setCurrentStyles() {
return {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
}
NgModel:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<input [(ngModel)]="myTechnology"> {{ myTechnology }}
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Angular 4 Love Affair';
technologies = ['Angular 4', 'Angular 5 RC1', 'Angular Material', 'Angular CLI'];
myTechnology = this.technologies[1];
}
Same result with binding to input element's value property and input event:
<input [value]="myTechnology" (input)="myTechnology=$event.target.value"> {{ myTechnology }}
ngModel desugared:
<input [ngModel]="myTechnology" (ngModelChange)="myTechnology=$event"> {{ myTechnology }}
ngModel, standard form:
<input [(ngModel)]="myTechnology"> {{ myTechnology }}
Force ngModel value to uppercase:
<input [ngModel]="myTechnology" (ngModelChange)="setUpperCaseName($event)"> {{ myTechnology }}
<input bindon-ngModel="myTechnology"> {{ myTechnology }}
setUpperCaseName(input: string) {
this.myTechnology = input.toUpperCase();
}
Structural Directives:
- NgIf: conditionally add or remove an element from the DOM.
- NgFor: repeat a template for each item in a list.
- NgSwitch: set of directivesthat switch among alternative views.
NgIf:
Template:
<app-technology-detail *ngIf="technology.isActive" [technology]="technology">
</app-technology-detail>
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>
<app-technology-detail [class.hidden]="isSpecial"></app-technology-detail>
<div [style.display]="isSpecial ? 'block' : 'none'">Show/Write Angular code with style</div>
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
Component:
isSpecial = true;
<div *ngIf="myTechnology">Hello {{ myTechnology.name }}</div>
<div *ngIf="nullTechnology">Hello {{ nullTechnology.name }}</div>
NgFor (technology after let is a template input variable):
<div *ngFor="let technology of technologies">{{ technology.name }}</div>
<app-technology-detail *ngFor="let technology of technologies" [technology]="technology">
</app-technology-detail>
*ngFor with index:
<div *ngFor="let technology of technologies; let i = index;">
{{ i + 1 }} - {{ technology.name }}
</div>
*ngFor with trackBy:
Template:
<div *ngFor="let technology of technologies; trackBy: trackByTechnologies;">
{{ technology.id }} {{ technology.name }}
</div>
Component:
trackByTechnologies(index: number, technology: Technology) {
return technology.id;
}
NgSwitch directive:
Multiple emotional components:
import { Component, Input } from '@angular/core';
import { Technology } from './app.component';
@Component({
selector: 'app-awesome-technology',
template: `Wow. You like {{ technology.name }}. What an awesome technology ... just like you.`
})
export class AwesomeTechnologyComponent {
@Input() technology: Technology;
}
@Component({
selector: 'app-happy-technology',
template: `You are a happy technology: {{ technology.name }}. May the sun always shine over you.`
})
export class HappyTechnologyComponent {
@Input() technology: Technology;
}
@Component({
selector: 'app-great-technology',
template: `You like {{ technology.name }}? Such a great technology. Are you great too?`
})
export class GreatTechnologyComponent {
@Input() technology: Technology;
}
@Component({
selector: 'app-unknown-future-killer-technology',
template: ` {{ message }}`
})
export class UnknownFutureKillerTechnologyComponent {
@Input() technology: Technology;
get message() {
return this.technology && this.technology.name ?
`${this.technology.name} is strange and mysterious.` : 'Code with passion!';
}
}
export const technologySwitchComponents =
[ AwesomeTechnologyComponent, HappyTechnologyComponent,
GreatTechnologyComponent, UnknownFutureKillerTechnologyComponent ];
AppComponent:
import { Component } from '@angular/core';
export class Technology {
constructor(public id: number, public name: string, public emotion: string) {}
}
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<p>Pick your favorite technology</p>
<div>
<label *ngFor="let tech of technologies">
<input type="radio" name="technologies" [(ngModel)]="currentTechnology"
[value]="tech"> {{ tech.name }}
</label>
</div>
<div [ngSwitch]="currentTechnology?.emotion">
<app-awesome-technology *ngSwitchCase="'awesome'" [technology]="currentTechnology">
</app-awesome-technology>
<app-happy-technology *ngSwitchCase="'happy'" [technology]="currentTechnology">
</app-happy-technology>
<app-great-technology *ngSwitchCase="'great'" [technology]="currentTechnology">
</app-great-technology>
<app-unknown-future-killer-technology *ngSwitchDefault [technology]="currentTechnology">
</app-unknown-future-killer-technology>
</div>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Angular 4 Love Affair';
currentTechnology: Technology;
technologies = [
new Technology(1, 'Angular 4', 'awesome'),
new Technology(2, 'Angular 5', 'happy'),
new Technology(3, 'Angular Material', 'great'),
new Technology(4, 'Angular CLI', 'unknown')
];
myTechnology = this.technologies[0];
}
Switch directives work with native elements and web components too. Like this:
<div *ngSwitchCase="'happy'">You are a happy technology: {{ currentTechnology.name }}. May the sun always shine over you.</div>
Template reference variable (#var):
<input #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>
How reference variable gets its value:
Template:
<form (ngSubmit)="onSubmit(technologyForm)" #technologyForm="ngForm">
<div class="form-group">
<label for="name">Name
<input class="form-control" name="name" required [(ngModel)]="myTechnology.name">
</label>
</div>
<button type="submit" [disabled]="!technologyForm.form.valid">Submit</button>
</form>
<div [hidden]="!technologyForm.form.valid">
{{ submitMessage }}
</div>
Component:
submitMessage: any;
onSubmit(technologyForm): void {
this.submitMessage = technologyForm.value.name;
}
Template reference variable, ref- prefix as alternative to #:
<input ref-email placeholder="email">
<button (click)="sendEmail(email.value)">Email</button>
Input and Output properties:
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Technology } from './app.component';
@Component({
selector: 'app-technology-detail',
template: `
<span>{{ technology.name }}</span>
<button (click)="delete()">Delete</button><br>
`
})
export class TechnologyDetailComponent {
@Input() technology;
@Output() deleteRequest = new EventEmitter<Technology>();
delete() {
this.deleteRequest.emit(this.technology);
}
}
Identify members in inputs and outputs arrays of directive metadata:
import { Component, EventEmitter } from '@angular/core';
import { Technology } from './app.component';
@Component({
inputs: ['technology'],
outputs: ['deleteRequest'],
selector: 'app-technology-detail',
template: `
<span>{{ technology.name }}</span>
<button (click)="delete()">Delete</button><br>
`
})
export class TechnologyDetailComponent {
technology;
deleteRequest = new EventEmitter<Technology>();
delete() {
this.deleteRequest.emit(this.technology);
}
}
AppComponent:
import { Component } from '@angular/core';
export class Technology {
constructor(public id: number, public name: string, public emotion: string) {}
}
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<app-technology-detail *ngFor="let technology of technologies" [technology]="technology"
(deleteRequest)="delete($event)">
</app-technology-detail>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Angular 4 Love Affair';
technologies = [
new Technology(1, 'Angular 4', 'awesome'),
new Technology(2, 'Angular 5', 'happy'),
new Technology(3, 'Angular Material', 'great'),
new Technology(4, 'Angular CLI', 'unknown')
];
delete(technology) {
const idx = this.technologies.indexOf(technology);
this.technologies.splice(idx, 1);
}
}
Aliasing input/output properties:
<div (myClick)="clickMessage=$event" clickable>Click Me with myClick</div>
{{ clickMessage }}
</div>
@Output alias:
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
@Directive({selector: '[myClick]'})
export class ClickDirective {
@Output('myClick') clicks = new EventEmitter<string>();
toggle = false;
constructor(el: ElementRef) {
el.nativeElement.addEventListener('click', () => {
this.toggle = !this.toggle;
this.clicks.emit(this.toggle ? 'Click!' : '');
});
}
}
Alias property names, like this:
import { Directive, ElementRef, EventEmitter} from '@angular/core';
@Directive({
selector: '[myClick]',
outputs: ['clicks:myClick'] // <-- propertyName:alias
})
export class ClickDirective {
clicks = new EventEmitter<string>();
toggle = false;
constructor(el: ElementRef) {
el.nativeElement.addEventListener('click', () => {
this.toggle = !this.toggle;
this.clicks.emit(this.toggle ? 'Click!' : '');
});
}
}
Pipe ( | ) operator:
<div>Title through uppercase pipe: {{ title | uppercase }}</div>
Multiple Pipes:
<div>Title through pipe chain: {{ title | uppercase | lowercase }}</div>
Parameters to a Pipe(Elvis Operator or Safe Traversal Operator is in the mix):
<div>Birthdate: {{ myTechnology?.birthdate | date:'longDate' }}</div>
JSON Pipe(useful for debugging):
<div>My JSON: {{ myTechnology | json }}</div>
Safe Navigation Operator(guard against null and undefined values):
<div>My technology's name is {{ myTechnology?.name }}</div>
Non Null Assertion Operator ( ! ):
<div *ngIf="myTechnology">My rocket is {{ myTechnology!.rocket }}</div>