5. Template Syntax
Interpolation:
<p>My favorite technology is {{ myTechnology }}</p>
<h1>{{ title }}</h1>
<img src="{{ imageUrl }}" class="img-round" alt="image">
<p>The sum of 2 + 2 is {{ 2 + 2 }}</p>
<p>The sum of 2 + 2 is not {{ 2 + 2 + getNumber() }}</p>
Expression Context:
{{ title }}
<span [hidden]="isUnchanged">changed</span>
<!-- let technology is template input variable -->
<div *ngFor="let technology of technologies">{{ technology.name }}</div>
<!-- #technologyName is template reference variable -->
<input #technologyName (keyup)="0"> {{ technologyName.value }}
Template Statements:
<button (click)="delete()">Delete</button>
Statement Context:
<button (click)="onSave($event)">Save</button>
<form #technologyForm="ngForm" (ngSubmit)="onSubmit(technologyForm)">
<input>
<button type="submit">submit</button>
</form>
HTML6 and/or HTML7 standards, a new element or component:
<app-technology-detail></app-technology-detail>
Data Binding:
<button [disabled]="isUnchanged">Save</button>
Property Binding(notice square brackets):
<img [src]="imageUrl" class="img-round" alt="image">
<app-technology-detail [technology]="myTechnology"></app-technology-detail>
<div [ngClass]="{'special': isSpecial }">my polymer princess</div>
Event Binding(notice parentheses):
<button (click)="onSave()">Save</button>
<app-technology-detail (newVersionRequest)="newTechnology($event)"></app-technology-detail>
<div (myClick)="clicked=$event">Click me</div> {{ clicked }}
Two-Way Binding:
<input [(ngModel)]="name"> {{ name }}
Attribute Binding(the exception, we are all exceptions and exceptional):
<button [attr.aria-label]="help">Help</button>
Class Binding:
<div [class.special]="isSpecial">we are all special</div>
Style Binding:
<button [style.color]="isSpecial ? 'red' : 'green'">Write Angular Code With Style</button>
One-time String Initialization:
<app-technology-detail prefix="You are my princess" [technology]="myTechnology"></app-technology-detail>
Property Binding Or Interpolation:
<img [src]="imageUrl" class="img-round" alt="princess image">
<img src="{{ imageUrl }}" class="img-round" alt="princess image">
<h1>{{ title }}</h1>
<h1 [innerHTML]="title"></h1>
Content Security(Angular sanitizes HTML):
<p><span>{{ evilScript }}</span></p>
<p><span [innerHTML]="evilScript"></span></p>
evilScript = 'Template <script>Protractor Style Guide; testing best practices with protractor.</script>Syntax';
Attribute Binding(revisited):
<table border="3">
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
<tr><td>Three</td><td>Four</td></tr>
</table>
<button [attr.aria-label]="actionName">{{ actionName }} with Aria</button>
Class Binding(revisited):
<div [class]="special">she is special</div>
<div [class.special]="isSpecial">this class binding is special</div>
<div class="special" [class.special]="!isSpecial">this one is not so special</div>
<div [ngClass]="special">ngClass is special</div>
Style Binding(revisited):
<button [style.color]="isSpecial ? 'red' : 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan' : 'grey'">Save</button>
<button [style.font-size.em]="isSpecial ? 3 : 1">Big</button>
<button [style.fontSize.%]="!isSpecial ? 150 : 50">Small</button>
<button [ngStyle]="special">Write Angular Code With Style</button>
Event Binding(revisited):
<button (click)="onSave()">Save Her</button>
<button on-click="onSave()">Save Canonical Form</button>
<div (myClick)="clickMessage=$event">Click Me With myClick</div> {{ clickMessage }}
<input [value]="myTechnology" (input)="myTechnology=$event.target.value">{{ myTechnology }}
Custom Events with EventEmitter:
<p>
{{ prefix }} {{ technology }}
</p>
<button (click)="newVersion()">New Version</button>
export class TechnologyDetailComponent {
@Input() prefix: string;
@Input() technology: string;
@Output() newVersionRequest = new EventEmitter<string>();
newVersion() {
this.newVersionRequest.emit('Angular 6');
}
}
<app-technology-detail [technology]="myTechnology" (newVersionRequest)="newVersion($event)">
</app-technology-detail>
newVersion(payload) {
console.log(payload);
}
Two-Way Binding:
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-sizer',
template: `
<div>
<button (click)="decrease()" title="smaller">-</button>
<button (click)="increase()" 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>();
decrease() { this.resize(-1); }
increase() { this.resize(+1); }
resize(delta: number) {
this.size = Math.min(42, Math.max(8, +this.size + delta));
this.sizeChange.emit(this.size);
}
}
<app-sizer [(size)]="mySizePx"></app-sizer>
<div [style.font-size.px]="mySizePx">Resizable Text</div>
mySizePx = 15;
Angular Desugars SizerComponent To This:
<app-sizer [size]="mySizePx" (sizeChange)="mySizePx=$event"></app-sizer>
Built-In Directives
Attribute Directives
- NgClass
- NgStyle
- NgModel
NgClass:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<div class="left">
<div [ngClass]="isSpecial">This class binding is special</div>
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
</div>
<div class="right">
<img [src]="imageUrl" class="img-round" alt="princess image">
</div>
`,
styles: [`.mySpecial { color: red; }
.saveable { color: green; }
.modified { font-family: "Brush Script MT"; }
.special { font-weight: bold; font-size: x-large; }
`]
})
export class AppComponent {
title = 'Tour of Technologies';
imageUrl = '../assets/polymer6.jpg';
isSpecial = 'mySpecial';
canSave = true;
isUnchanged = false;
currentClasses: {};
setCurrentClasses() {
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
constructor() {
this.setCurrentClasses();
}
}
NgStyle:
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
This div is x-large.
</div>
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
setCurrentStyles() {
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
NgModel:
<input [(ngModel)]="myTechnology"> {{ myTechnology }}
<input [value]="myTechnology" (input)="myTechnology=$event.target.value"> {{ myTechnology }}
<input [ngModel]="myTechnology" (ngModelChange)="myTechnology=$event"> {{ myTechnology }}
<input [ngModel]="myTechnology" (ngModelChange)="setUpperCase($event)"> {{ myTechnology }}
Structural Directives
- NgIf
- NgFor
- NgSwitch
NgIf:
<app-technology-detail [technology]="technology.name" *ngIf="technology.isActive">
</app-technology-detail>
Show/Hide:
<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>
<!--TechnologyDetail is in the DOM but hidden -->
<app-technology-detail [technology]="myTechnology" [class.hidden]="isSpecial"></app-technology-detail>
<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
Guard Against Null:
<div *ngIf="myTechnology">Greet {{ myTechnology }}</div>
<div *ngIf="nullTechnology">Greet {{ nullTechnology }}></div>
NgFor:
<div *ngFor="let technology of technologies">{{ technology.name }}</div>
<app-technology-detail *ngFor="let technology of technologies" [technology]="technology.name">
</app-technology-detail>
*ngFor with index:
<div *ngFor="let technology of technologies; let i = index;">
{{ i + 1 }} - {{ technology.name }}
</div>
*ngFor with trackBy:
<div *ngFor="let technology of technologies; trackBy: trackByTechnologies">
({{ technology.id }}) {{ technology.name }}
</div>
trackByTechnologies(index: number, technology: Technology) {
console.log(technology.id);
return technology.id;
}
NgSwitch:
<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-confused-technology *ngSwitchCase="'confused'" [technology]="currentTechnology"></app-confused-technology>
<div *ngSwitchCase="'confused'">Are you as confused as {{ currentTechnology.name }}?</div>
<app-unknown-killer-technology *ngSwitchDefault [technology]="currentTechnology"></app-unknown-killer-technology>
</div>
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-confused-technology',
template: `Are you as confused as {{ technology.name }}?`
})
export class ConfusedTechnologyComponent {
@Input() technology: Technology;
}
@Component({
selector: 'app-unknown-killer-technology',
template: `{{ message }}`
})
export class UnknownKillerTechnologyComponent {
@Input() technology: Technology;
get message() {
return this.technology && this.technology.name ? `${ this.technology.name } is strange and mysterious.`
: 'Are you feeling indecisive?';
}
}
export const technologySwitchComponents = [ AwesomeTechnologyComponent, HappyTechnologyComponent,
ConfusedTechnologyComponent, UnknownKillerTechnologyComponent ];
Template Reference Variables (#var):
<input #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>
<form (ngSubmit)="onSubmit(technologyForm)" #technologyForm="ngForm">
<div class="form-group">
<label for="name">Name
<input class="form-control" name="name" required [(ngModel)]="currentTechnology">
</label>
</div>
<button type="submit" [disabled]="!technologyForm.form.valid">Submit</button>
</form>
<div [hidden]="!technologyForm.form.valid">
{{ submitMessage }}
</div>
submitMessage = '';
onSubmit(techForm) {
this.submitMessage = JSON.stringify(techForm.value);
}
<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>
@Input && @Output:
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-technology-detail',
template: `
<p>
{{ technology }}
</p>
<button (click)="newVersion()">New Version</button>
`
})
export class TechnologyDetailComponent {
@Input() technology: string;
@Output() newVersionRequest = new EventEmitter<string>();
newVersion() {
this.newVersionRequest.emit('Angular 6');
}
}
Alternatively:
import { Component, EventEmitter } from '@angular/core';
@Component({
selector: 'app-technology-detail',
template: `
<p>
{{ technology }}
</p>
<button (click)="newVersion()">New Version</button>
`,
inputs: ['technology'],
outputs: ['newVersionRequest']
})
export class TechnologyDetailComponent {
newVersionRequest = new EventEmitter<string>();
newVersion() {
this.newVersionRequest.emit('Angular 6');
}
}
AppComponent:
<app-technology-detail [technology]="myTechnology"
(newVersionRequest)="newVersion($event)">
</app-technology-detail>
{{ message }}
message = '';
newVersion(payload) {
this.message = payload;
}
Aliasing Input/Output Properties:
<div (myClick)="clickMessage=$event">Click with myClick</div>
{{ clickMessage }}
@Output('myClick') clicks = new EventEmitter<string>();
@Directive({
selector: '[myClick]',
outputs: ['clicks:myClick']
})
Pipes:
<div>myTechnology through uppercase pipe: {{ myTechnology | uppercase }}</div>
<div>myTechnology through pipe chain: {{ myTechnology | uppercase | lowercase }}</div>
<div>Birthdate: {{ myTechnology.birthdate | date:'longDate' }}</div>
<div>{{ myTechnology | json }}</div>
Safe Navigation Operator(formerly Elvis Operator):
<div>My Technology's name is {{ myTechnology?.name }}</div>
Non-Null Assertion Operator:
<div *ngIf="myTechnology">
My technology's name is {{ myTechnology!.name }}
</div>
LikeComponent:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-like',
template: `
<p>The will to win is nothing without the will to prepare.</p>
<p>Likes: {{ likes }}</p>
<button class="btn btn-primary"
[class.btn-danger]="isSelected" (click)="onClick()">Like</button>
`
})
export class LikeComponent {
@Input() likes = 0;
isSelected = false;
onClick() {
this.isSelected = !this.isSelected;
this.likes += (this.isSelected) ? 1 : -1;
}
}
Property Binding (Square Bracket Notation):
<div class="col">
<h2 [textContent]="title"></h2>
<img class="w-100 p-3 rounded-circle" [src]="imageUrl" />
</div>
title = 'List of Technologies';
imageUrl = './assets/polymer1.jpg';
Attribute Binding:
<table>
<tr>
<td [attr.colspan]="colSpan">this column spans 4 columns</td>
</tr>
</table>
colSpan = 4;
Class Binding:
<button class="btn btn-danger" [class.active]="isActive">Save Her</button>
isActive = true;
Style Binding:
<button [style.backgroundColor]="isActive ? 'red' : 'green'">Save Her</button>
isActive = true;
Event Binding:
<div (click)="onDivClicked()">
<button class="btn btn-success" (click)="onSave($event)">Save Her</button>
</div>
onDivClicked() {
console.log('div was clicked');
}
onSave(event) {
event.stopPropagation();
console.log('save button was clicked', event);
}
Event Filtering:
<input #box (keyup.enter)="onKey(box.value)" (keyup)="onKey(box.value)" />
{{ message }}
message = '';
onKey(value: string) {
this.message += value + '|';
}
Template Reference Variable:
<input #text (keyup.enter)="onKey(text.value)" />
{{ message }}
message = '';
onKey(text: string): void {
this.message = text;
}
Two Way Binding:
<input [(ngModel)]="message" />
{{ message }}