0% found this document useful (0 votes)
32 views288 pages

Further Angular

Uploaded by

Lehel Sipos
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views288 pages

Further Angular

Uploaded by

Lehel Sipos
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 288

Further

Angular
Mallon Associates
June 2020

Observers and Observables

• Observable publishes stream of data / events

• Observers handles data stream generated by Observable


– aka a subscriber

• Stream may be processed in between

Observable

filter()

Subscriber
Reactive Programming

• Observer / Observable
– is a well-known design pattern
– is a push model
– supported by a range of languages
– good for asynchronous processing

• Need to be able to
– cancel notifications
– be notified of end of data stream
– to replay data etc.

• Reactive Programming
– is about creating responsive (fast) event-driven applications
– more than just the Observer pattern
– range of libraries support reactive programming
– Angular uses the RxJS library
Observers and Observables

• Observable publishes stream of data / events

• Observers handles data stream generated by Observable


– aka a subscriber

• Stream may be processed in between

Observable

filter()

Subscriber
What is Rxjs?

• Provides an implementation of the Observable type


– needed until the type becomes part of the Angular language
– and, until browsers support it

• Provides utility functions


– for creating and working with observables

• These functions can be used for:


– converting existing code for async operations into observables
– iterating through the values in a stream
– mapping values to different types
– filtering streams
– composing multiple streams

• Library documentation
– see https://rxjs-dev.firebaseapp.com/
Observables

• Observable
– defined in rxjs
– supplies a stream of data values

• Subscribers
– can subscribe to an observable
– will be notified when a value is available

• Observable additional features


– can be cancelled
– and operators such as map, forEach, reduce, retry, replay
Defining Observables

• A handler for receiving observable notifications

• Must implement the Observer interface


– an object that defines callback methods
– to handle the three types of notifications that an observable can send

next Required: Handler for each delivered value

error Optional: Handler for an error notification

complete Optional. Handler for execution-complete notification


Creating Observables

• Creating observables can be done using:

• of
– converts the arguments to an observable sequence
• from
– creates an Observable from:
– an Array, an array-like object, a Promise, an iterable object
– or an Observable-like object
• interval
– creates an Observable that emits sequential numbers
– every specified interval of time, on a specified ShedulerLike
• merge
– creates an output Observable
– concurrently emits all values from every given input Observable
Subscribe Using Observer
app.component.ts
import { Component } from '@angular/core';
import { of } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}
// Create simple observable that emits three values
const myObservable = of(1, 2, 3);
// Create observer object
const myObserver = {
next: x => console.log(‘Got next value: ' + x),
error: err => console.error(‘Got an error: ' + err),
complete: () => console.log(‘Got a complete notification'),
};
// Execute with the observer object
myObservable.subscribe(myObserver);
Subscribe Using Observer
Subscribe Using Observer
app.component.ts
import { Component } from '@angular/core';
import { of } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

// Create simple observable that emits three values


const myObservable = of(1, 2, 3);

myObservable.subscribe(
x => console.log(‘Got a next value: ' + x),
err => console.error(‘Got an error: ' + err),
() => console.log(‘Got a complete notification')
);
Subscribe Using Observer
Observable Creation Functions

• Creating an observable from a promise:


app.component.ts
import { Component } from '@angular/core';
import { from } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

// Create an Observable out of a promise


const data = from(fetch('/'));
// Subscribe to begin listening for async result
data.subscribe({
next(response) { console.log(response); },
error(err) { console.error('Error: ' + err); },
complete() { console.log('Completed'); }
});
Observable Creation Functions

• Creating an observable from a promise:


Observable Creation Functions

• Creating an observable from a counter:


app.component.ts
import { Component } from '@angular/core';
import { interval } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

// Create an Observable that will publish a value on an interval


const secondsCounter = interval(1000);
// Subscribe to begin publishing values
secondsCounter.subscribe(n =>
console.log(`It's been ${n} seconds since subscribing!`));
Observable Creation Functions

• Creating an observable from a counter:


Observable Creation Functions

• Creating an observable with a constructor:


app.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

// This function runs when subscribe() is called


function sequenceSubscriber(observer) {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
return {unsubscribe() {}};
}
...
Observable Creation Functions

• Creating an observable with a constructor:


app.component.ts
...

// Create a new Observable that will deliver the above sequence


const sequence = new Observable(sequenceSubscriber);

// execute the Observable and print the result of each notification


sequence.subscribe({
next(num) { console.log(num); },
complete() { console.log('Finished sequence'); }
});
Observable Creation Functions

• Creating an observable with a constructor:


Operators

• Functions that build on the observables


– to enable sophisticated manipulation of collections

• RxJS defines operators such as:


– map(), filter(), concat(), and flatMap()

• Operators take configuration options


– they return a function that takes a source observable

• When executing this returned function


– operator observes the source observable’s emitted values
– transforms them
– returns a new observable of those transformed values
Operators

• Using the map() operator app.component.ts


import { Component } from '@angular/core';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

const nums = of(1, 2, 3);

const squareValues = map((val: number) => val * val);


const squaredNums = squareValues(nums);

squaredNums.subscribe(x => console.log(x));


Operators

• Using the map() operator


Pipes

• Use to link operators together


– combine multiple functions into a single function

• pipe() function:
– takes the functions to combine as its arguments
– returns a new function which runs the composed functions
– functions are run in sequence

• Need to call subscribe()


– to produce a result through the pipe
Pipes

• Using the pipe() operator app.component.ts


import { Component } from '@angular/core';
import { of, pipe } from 'rxjs';
import { filter, map } from 'rxjs/operators';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

const nums = of(1, 2, 3, 4, 5);

...
Pipes

• Using the pipe() operator


app.component.ts
...

// Create a function that accepts an Observable.


const squareOddVals = pipe(
filter((n: number) => n % 2 !== 0),
map(n => n * n)
);

// Create an Observable that will run the filter and map functions
const squareOdd = squareOddVals(nums);

// Subscribe to run the combined functions


squareOdd.subscribe(x => console.log(x));
Pipes

• Using the pipe() operator


Pipes

• Using the pipe() method on the Observable


import { Component } from '@angular/core'; app.component.ts
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rxjs-example';
}

const squareOdd = of(1, 2, 3, 4, 5)


.pipe(
filter(n => n % 2 !== 0),
map(n => n * n)
);
squareOdd.subscribe(x => console.log(x));
Observables in Angular

• Angular makes use of observables as an interface


– to handle a variety of common asynchronous operations

• For example:
– custom events sending output data from child to a parent component

– HTTP module for AJAX requests and responses

– Router and Forms modules listen for & respond to user-input events

– AsyncPipe subscribes to an observable & returns latest value emitted


Forms

• HTML provides basic forms features


– input field of different types

• Supporting
– simple validation
– submission of data to URLs

• But limited for real world applications


– custom validation rules
– client-side behavior e.g. data transformations
– approaches to data submission for SPAs

• Many libraries available


– for many technologies / tools / frameworks
Angular Forms

• Fields are treated as first-class citizens


– with fine-grained control over form data
– fully integrated with framework

• Provides two approaches to working with forms


– template driven approach and reactive approach
– supported by different APIs

• Template driven forms


– view template defines structure of form
– format of fields and validation rules

• Reactive forms
– form model created programmatically
Reactive Forms

• Two step process


– create the model programmatically
– connect the model to the view template using directives
Use the ReactiveFormsModule
– provides components, directives and providers
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';

@NgModule({
declarations: [ AppComponent ],
imports: [
BrowserModule,
ReactiveFormsModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
Form Model

• Underlying data structure for Form data


– constructed out of forms classes
• FormControl
– atomic form unit
– typically corresponds to a single <input> element
– but can represent a calendar or slider etc.
– keeps current value of element it corresponds to
• FormGroup
– represents a part of a form
– is a collection of FormControls
– status of group is aggregate of components
• FormArray
– variable length collection of FormContols
– e.g. to allow user to enter variable number of family members
Reactive Form Example

• Angular provides a form and input directive


– can be used in view template
app.component.html
<form [formGroup]="loginForm" (ngSubmit)="login()">
<label>username</label>
<input type="text" name="username"
formControlName="username">
<br/>
<label>password</label>
<input type="password" name="password"
formControlName="password">
<br/>
<button type="submit">Submit Details</button>
</form>
Reactive Form Example

• Associated component
– imports form classes from @angular/forms
app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, FormBuilder } from
'@angular/forms';

@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
username = new FormControl('usrname')
password = new FormControl('')

constructor(private builder: FormBuilder) { }

– from input field names properties on object


Reactive Form Example

constructor(private builder: FormBuilder) { }

loginForm: FormGroup = this.builder.group({


username: this.username,
password: this.password
});

login() {
console.log(this.loginForm.value);
console.log(`Username: ${this.username}`);
console.log(`Password: ${this.password}`)
}
}
Reactive Form Example

• Running the application


Validating Reactive Forms

• Many validator functions provided

• Validators class
– provides validation function provided as static methods
– including required, minLength, maxLength and pattern
– can be imported from @angular/forms

• Specified when model objects are instantiated


username = new FormControl('usrname', Validators.required)

• Can provide a list of validators


password = new FormControl('',
[Validators.required, Validators.minLength(6)])
Validating Reactive Forms

• To test form element validity use valid property


let pwdIsValid: boolean = this.password.valid

• Can obtain list of errors user errors property


– returns JavaScript error objects
– has property with same name as the validator

let errors: {[key: string] : any} = this.password.errors;

– different validators provider different information


– e.g. minLength indicates the required minimum length and the actual
length
– required just returns whether it was required or not
Validating Reactive Forms Example

• AppComponent specifying validators


app.component.ts
import { Component } from '@angular/core';
import { Validators, FormGroup, FormControl, FormBuilder }
from '@angular/forms';

@Component( {
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
username = new FormControl('usrname', Validators.required)
password = new FormControl( '',
[Validators.required, Validators.minLength(6)])

constructor( private builder: FormBuilder ) { }

loginForm: FormGroup = this.builder.group( {


username: this.username,
password: this.password
});
Validating Reactive Forms Example

login() {
let unameIsValid = this.username.valid
if ( !unameIsValid ) {
console.log( this.username.errors )
}
let pwdIsValid: boolean = this.password.valid
if ( !pwdIsValid ) {
let errors: { [key: string]: any } =
this.password.errors;
console.log( 'Pwd Field Errors: ' +
JSON.stringify( errors ) );
}
console.log( this.loginForm.value )
}
}
Validating Reactive Forms Example

• Can also use in view template


– query control object with hasError
app.component.html
<form [formGroup]="loginForm" (ngSubmit)="login()">
<label>username</label>
<input type="text" name="username"
formControlName="username">
<br/>
<label>password</label>
<input type="password" name="password"
formControlName="password">
<br/>
<button type="submit">Submit Details</button>
<div [hidden]="!password.hasError('required')">
The password is required.
</div>
</form>
Validating Reactive Forms Example

• Running the example


Reactive Forms Custom Validation

• Validator functions conform to the ValidatorFn interface


interface ValidatorFn {
(c: AbstractControl): {[key: string]: any};
}

• Can define functions to perform validation


function hasXYZ(input: FormControl) {
// test condition
// return null or error object
}

• Passing values to validation functions


– one function that returns validator function
function minLength(minimum) {
return function(input) {
return
input.value.length >= minimum ? null : {minLength: true};
};
}
Forms Custom Validation Example

• Define hasExclamation function


– can be used with any control
– passed to control constructor
app.component.ts
function hasExclamation(input: FormControl) {
const hasExclamation = input.value.indexOf('!') >= 0;
return hasExclamation ? null : {needsExclamation: true};
}

@Component( {
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
username = new FormControl('usrname', Validators.required)
password = new FormControl( '',
[Validators.required, hasExclamation])

constructor( private builder: FormBuilder ) { }


Forms Custom Validation Example

loginForm: FormGroup = this.builder.group( {


username: this.username,
password: this.password
});

login() {
let unameIsValid = this.username.valid
if ( !unameIsValid ) {
console.log( this.username.errors )
}
let pwdIsValid: boolean = this.password.valid
if ( !pwdIsValid ) {
let errors: { [key: string]: any } =
this.password.errors;
console.log( 'Pwd Field Errors: ' +
JSON.stringify( errors ) );
}
console.log( this.loginForm.value )
}
}
Forms Custom Validation Example

• Can also use in view template


– query control object with hasError
app.component.html
<form [formGroup]="loginForm" (ngSubmit)="login()">
<label>username</label>
<input type="text" name="username"
formControlName="username">
<br/>
<label>password</label>
<input type="password" name="password"
formControlName="password">
<br/>
<button type="submit">Submit Details</button>
<div [hidden]="!password.hasError('needsExclamation')">
The password must include an Exclamation mark.
</div>
</form>
Forms Custom Validation Example

• Using custom validation


Comparison of Approaches

• Both approaches utilize a model


– underlying data structure for storing form data
– created implicitly by template-driven approach
– created programmatically in reactive approach

• Model is not an arbitrary object


– must be type defined by @angular/forms module
– such as FormControl, FormGroup and FormArray

• Both approaches require a HTML template


– Angular does not generate a view automatically
Comparison of Approaches

• Template driven approach


– limited to HTML syntax
– typically used for simpler scenarios

• Reactive approach
– suitable for more complex scenarios
– data structures handled in code
– link HTML template elements to model
– using special directives prefixed with form
Using Pipes

• Pipe is a template element


– that filters / transforms data
Data Source
e.g. component
data or method

Pipe

HTML Template
Using Pipes

• Can apply multiple pipes in a chain

Data Source
e.g. component
data or method

Pipe

Pipe

HTML Template
Predefined Pipes

• Several predefined pipes available


– each implemented by a class e.g. CurrencyPipe, DatePipe
– and name used in a HTML template e.g. currency, date

• CurrencyPipe
– transforms number into desired currency
– name is currency

• DatePipe
– displays a date in different formats
– name is date

• UpperCasePipe / LowerCasePipe
– converts input into upper case / lower case
– name is uppercase / lowercase
Working with Pipes

• The '|' used to link pipe to data


app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `<p>Total price: {{ price | currency }}</p>`
})
export class AppComponent {
price = 100.12348;
}

• AppComponent defined price instance variable


– accessed within HTML template
– built in currency filter applied to price
– renders currency in USD to 2 decimal places
Working with Pipes

• Result of apply currency pipe to price


Pipes and Parameters

• Parameters passed to pipes


– follow pipe with ':' and parameter value
– can have multiple parameters each separate by ':"

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `<p>Total price: {{ price | currency: "GBP" }}</p>
<p>Total price: {{ price | currency: "GBP": true}}</p>`
})
export class AppComponent {
price = 100.12348;
}
Pipes and Parameters

• Result of applying param


Chaining Pipes

• Can combine pipes together

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<p>Total price: {{ price | currency | lowercase }}</p>
`
})
export class AppComponent {
price = 100.12348;
}
Chaining Pipes

• Result of chaining pipes


Custom Pipes

• Can define new pipes


– as utilities or application specific

• Need to annotate class with @Pipe decorator


– providing the name of the pipe
@Pipe( {
name: '<pipename>'
})
export class <ClassName> ...

• Implement the angular/core PipeTransform interface


– PipeTransform interface defines a single method transform

interface PipeTransform {
transform(value: any, ...args: any[]) : any
}
Custom Pipes

• Custom pipe to format file sizes


import { Pipe, PipeTransform } from '@angular/core';

const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB'];


const FILE_SIZE_UNITS_LONG =
['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes'];

@Pipe( {
name: 'formatFileSize'
})
export class FormatFileSizePipe implements PipeTransform {

transform(sizeInBytes: number, longForm: boolean): string


{
const units = longForm
? FILE_SIZE_UNITS_LONG
: FILE_SIZE_UNITS;

let power = Math.round( Math.log( sizeInBytes ) /


Math.log( 1024 ) );
power = Math.min( power, units.length - 1 );
Custom Pipes

// size in new units


const size = sizeInBytes / Math.pow( 1024, power );

// keep up to 2 decimals
const formattedSize = Math.round( size * 100 ) / 100;

const unit = units[power];

return `${formattedSize} ${unit}`;


}

}
Using Custom Pipes

• Module imports the pipe

import { BrowserModule } from '@angular/platform-browser';


import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';


import { FormatFileSizePipe } from './format-file-size.pipe';

@NgModule({
declarations: [
AppComponent,
FormatFileSizePipe
],
imports: [ BrowserModule ],
bootstrap: [AppComponent]
})
export class AppModule { }
Using Custom Pipe

• AppComponent uses named pipe in template

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<div>
<p *ngFor="let f of fileSizes">{{f |
formatFileSize}}</p>
<p>{{ largeFileSize | formatFileSize: true }}</p>
</div>`
})
export class AppComponent {
fileSizes = [10, 1024, 10240];
largeFileSize = 10000000;
}
Using Custom Pipes

• Output generated from formatFileSize pipe


Stateful Pipes

• Two categories of Pipe

• Stateless Pipes
– pure functions
– filter input through to output
– without storing anything or having any side effects
– most pipes are stateless

• Stateful Pipes
– hold some state / data
– can influence processing of data based on state
– use with care
– AsyncPipe is a stateful pipe
Implementing Stateful Pipes

• Pipes are stateless by default


• Must set pure property of decorator to false
import { Pipe, PipeTransform } from '@angular/core';

@Pipe( {
name: 'logNumber',
pure: false
})
export class LogNumberPipe implements PipeTransform {
private lastNumber: number = 5;
private temp: number = this.lastNumber;

transform( n: number ): number {


this.temp = this.lastNumber;
this.lastNumber = n;
console.log("previousNumber= " + this.temp +
", n = " + n);
return n + 1;
}
}
Implementing Stateful Pipes

• Module declares the pipe and AppComponent


– references pipe in HTML template

import { Component } from '@angular/core';

@Component( {
selector: 'app-root',
template: `
<ul>
<li>The number is {{ total | logNumber }}</li>
</ul>`
})
export class AppComponent {
total = 1;
constructor() {
setInterval(() => {
this.total += 2;
}, 1000 );
}
}
Implementing Stateful Pipes

• Output generated from log number pipe


Projection

• Allows developers to create reusable components

• Allows content placed between components start and end


tags

• Can be projected into child's HTML template

• Makes use of the Shadow DOM

• Shadow DOM
– refers to the ability of the browser to include a subtree of DOM
elements
– into the rendering of a document, but not into the main document
DOM tree
Projection

• Content defined between the child start and end tags

app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<h2>My Application</h2>
<child>
This is the <i>content</i> from the parent.
</child>
`
})
export class AppComponent { }
Projection

• ng-content used to indicate projection insertion point

child.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'child',
template: `
<div style="border: 1px solid blue; padding: 1rem;">
<h4>Child Component</h4>
<ng-content></ng-content>
</div>`
})
export class ChildComponent { }
Projection

• A child having content projected from parent


Projection Selection

• It is possible to select the content to be projected

• Can be based on
– predefined directive selection or css classes

• Child with several different sections and div as content


<div>
<h4>App Component</h4>
<child>
<section>Section Content</section>
<div class="selectionkey">
div with selectionkey
</div>
<footer>Footer Content</footer>
<header>Header Content</header>
</child>
</div>
Projection Selection

child.component.ts
@Component({
ng-content select used
selector: 'child',
to identify content to
template: `
project
<div>
<h4>Child Component</h4>
<div style="border: 1px solid orange; padding: 1rem">
<ng-content select="header"></ng-content>
</div>
<div style="border: 1px solid green; padding: 1rem">
<ng-content select="section"></ng-content>
</div>
<div style="border: 1px solid blue; padding: 1rem">
<ng-content select=".selectionkey"></ng-content>
</div>
<div style="border: 1px solid purple; padding: 1rem">
<ng-content select="footer"></ng-content>
</div>
</div>`
})
export class ChildComponent { }
Projection Selection

• Child Component select specific content to project


@ContentChild and @ContentChildren

• Angular projects content into ng-content


– can access components generating content

App
Component

HelloList
Component

content

Hello Hello Hello


Component Component Component
@ContentChild and @ContentChildren

• Similar to @ViewChild and @ViewChildren


– but selects from projected content
app.component.ts
import { Component } from '@angular/core';

@Component( {
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {}

app.component.html
<h2>The message list</h2>
<hellolist>
<app-hello name="World"></app-hello>
<app-hello name="Other World"></app-hello>
<app-hello name="Last World"></app-hello>
</hellolist>
@ContentChild and @ContentChildren

• ng-content used to to include any content provided


hellolist.component.ts
import {Component, ContentChildren, QueryList}
from '@angular/core';
import {HelloComponent} from './hello.component';

@Component( {
selector: 'hellolist',
template: `<div><ng-content></ng-content></div>
<p><button (click)="onClickChangeColors()">
Randomize colours
</button></p>`
})
export class HelloListComponent {
@ContentChildren( HelloComponent )
children: QueryList<HelloComponent>;

onClickChangeColors() {
this.children.forEach( c=> c.randomizeColor() );
}
}
@ContentChild and @ContentChildren

• HelloComponent displays message in random colors

hello.component.ts
@Component({
selector: 'app-hello',
template: `<p [ngStyle]="{ 'color': color }">Hi
{{name}}</p>`
})
export class HelloComponent {
@Input() name: string;
colors: string[] = ['#ff0000', '#00FF00', '#0000FF',
'#00ffff', '#ff00ff', '#ffff00'];
color = this.colors[0];

private getRandomColor() {
return this.colors[Math.floor(Math.random() * 6)];
}
randomizeColor() {
this.color = this.getRandomColor();
}
}
@ContentChild and @ContentChildren

• Executing the random color HelloComponent example


Component Lifecycle

• Managed by Angular framework


– manages creation, rendering, data binding etc.

• Lifecycle hooks available


– ngOnChanges - called when an input binding value changes
– ngOnInit - after the first ngOnChanges
– ngDoCheck - after every run of change detection
– ngAfterContentInit - after component content initialized
– ngAfterContentChecked - after every check of content
– ngAfterViewInit - after component's view(s) are initialized
– ngAfterViewChecked - after every check of a component's view(s)
– ngOnDestroy - just before the component is destroyed

• Information on lifecycle hooks


– https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Component Lifecycle Example

• Component with lifecycle hooks


import {Component, SimpleChange, OnChanges, OnInit,
DoCheck, AfterContentInit,
AfterContentChecked, AfterViewInit,
AfterViewChecked, OnDestroy, EventEmitter
} from '@angular/core';

@Component( {
selector: 'my-component',
template: `
<div>
<h2>My Component</h2>
</div>
`
})
export class ChildComponent implements OnChanges,
OnInit, DoCheck, AfterContentInit,
AfterContentChecked, AfterViewInit,
AfterViewChecked, OnDestroy {
Component Lifecycle Example

ngOnInit() {
console.log( 'onInit' );
}

ngDoCheck() {
console.log( 'doCheck' );
}

ngAfterContentInit() {
console.log( 'afterContentInit' );
}

// Called after every change detection check


// of the component (directive) CONTENT
// Beware! Called frequently!
ngAfterContentChecked() {
console.log('afterContentChecked');
}
Component Lifecycle Example

ngAfterViewInit() {
console.log('afterViewInit');
}

// Called after every change detection component check


// Beware! Called frequently!
ngAfterViewChecked() {
console.log('afterViewChecked');
}

// Only called if there is an [input] variable set by parent


ngOnChanges(changes: {
[propertyName: string]: SimpleChange }) {
console.log('ngOnChanges' + changes);
}

ngOnDestroy() {
console.log('onDestroy');
}
}
Component Lifecycle Example

• Console output for example


ElementRef

• The ElementRef class allows direct access to the DOM


– provides access to the underlying DOM element
import {AfterContentInit, Component, ElementRef}
from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements AfterContentInit {
constructor(private elementRef: ElementRef) { }

ngAfterContentInit() {
const el = this.elementRef.nativeElement;
const tmp = document.createElement('p');
const node = document.createTextNode("Hi New World");
tmp.appendChild(node);
el.appendChild(tmp); <h1>My App</h1>
} <div>
} I am here <b>OK</b>
</div>
app.component.html
ElementRef

• Result of appending child directly to DOM element

• Angular documentation advises caution


– only use this API as a last resort
What is a Directive?

• HTML has a finite, limited vocabulary


– <ul> specifies render an unordered list
– <video> specifies to render a video

• Fixed nature of HTML


– makes developing modern web apps harder

• Developers use variety of technologies


– such as CSS and JavaScript to implement new features
– but results may not be obvious

• Image being able to extend HTML


– to generate output / change the display
– alter the behaviour or layout of DOM elements
What is a Directive?

• Directives are Angular solution to fixed nature of HTML


– "HTML enhanced for web apps"

• Directives elegant way to extend HTML


– add new attributes, tags etc.

• Enable reusable data and functions


– e.g. ngClick

• Simplify definition and rendering of HTML view

• Components really one class of custom directive!


Categories of Directive

• Three main types of directive in Angular


Angular 2
Directives

Attribute Structural
Component
directive directive

• Component
– a directive with a HTML template
– extend HTML as required
• Attribute Directives
– change the behavior of a component or element
– but don't affect the template
• Structural Directives
– modify how the template is rendered
– in order to change behavior of a component or element
Attribute Directives

• Change appearance or behavior of native DOM elements

• Typically independent of the DOM element

• Examples of attribute directives


– ngClass
– ngStyle
– both work on any element

• May be applied in response to user input, service data etc.


NgStyle Directive

• Built in directive
– used to modify the style attribute of an element

• Can be used within a component's template


– can bind a directive in similar manner to a property
– e.g. <p [ngStyle] ="{'color': 'blue'}">Some test</p>

• Can generate style information from component data


– allows style to change dynamically
– e.g. <p [ngStyle] ="componentStyles">Some test</p>

export class StyleExampleComponent {


borderStyle = 'black';

componentStyles = {'color': 'blue',


'border-color': this.borderStyle };
}
Structural Directives

• Handles way component or element renders


– through use of the template tag
• Several built in structural directives
– ngIf, ngFor and ngSwitch
• Template tag
– HTML element with special properties
– nested content is not rendered within page
– intended for client side code to handle

@Component({
selector: 'my-comp',
template: `
<template [myStructuralDirective]="somedata">
<p>
Under a structural directive.
</p>
</template>`
})
Structural Directives

• Have special syntax to make then easier to use


– can be prefixed with an '*'
– don't then need to use '[' and ']'

@Component({
selector: 'app-directive-example',
template: `
<p *structuralDirective="somedata">
Under a structural directive.
</p>
`
})

– *ngIf indicates this is a structural directive


– commonly asked question on forums – why the '*'
Custom Directives

• Can define custom directives

• Typescript class decorated with @Directive

• @Directive decorator defines metadata


– selector used with directive
– inputs list of class property names to data bind
– outputs list of class property names that expose output events
– providers list of providers available for directive

• Must belong to an NgModel

• Can implement lifecycle hooks


– similar to Components
Defining a Custom Directive

• Attribute directive
– to change the color of any content to red

change-color.directive.ts
import { Directive, ElementRef, Renderer }
from '@angular/core';

@Directive( {
selector: '[changecolor]'
})
export class ChangeColorDirective {
constructor( private el: ElementRef,
private render: Renderer ) {
this.render.setElementStyle(
this.el.nativeElement, 'color', 'red' );
}
Defining a Custom Directive

• Must declare directive as part of module


app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';


import { ChangeColorDirective } from './change-color.directive';

@NgModule( {
declarations: [
AppComponent,
ChangeColorDirective
],
imports: [BrowserModule],
bootstrap: [AppComponent]
})
Defining a Custom Directive

• Can use directive within component template


app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `<p changecolor>{{message}}</p>`
})
export class AppComponent {
message = 'Hello!';
}
Defining a Custom Directive

• Result of applying custom attribute directive


Passing data to directives

• It is also possible to pass data into a directive


– value bound to directive property marked with @Input()
@Directive( {
selector: '[changecolor]'
})
export class ChangeColorDirective {
@Input('changecolor') highlightColor: string;
constructor( private el: ElementRef,
private render: Renderer ) {
this.render.setElementStyle(
this.el.nativeElement, 'color',
highlightColor );
}
}

• Values specified when attribute directive is specified


– note must now surround with square brackets as value being bound

<p [changecolor]="blue">{{message}}</p>
Passing information

• Common to use services the share data


– between components
– between parent and child components etc.

• Can also use input bindings


– e.g. @Input info:string or @Input('myalias') info:string

• Can intercept property changes with a setter method


@Component({
...
})
export class ChildComponent {
private _name;
@Input()
set name(name: string) {
this._name = name.trim();
}
}
Reactive to Property Changes

• Can react to input property changes


– using ngOnChanges

@Component({
...
})
export class ChildComponent implements OnChanges {
@Input() private name;

ngOnChanges(changes: SimpleChanges}) {
for (let propName in changes) {
let changedProp = changes[propName];
console.log(
`Changed ${propName} from ${changedProp.previousValue}
to ${changedProp.currentValue}');
}
}
}
Parents Listen for Child Events

• Parent components can listen for child events

• Child Component exposes an EventEmitter property


– can use to emit events

• An EventEmitter
– is an instance variable decorated with @Output
– an instance of EventEmitter<type>()
– defined in @angular/core

• Parent can bind to event emitter property


– can react to those events
Component Event Example

• Count Component
– emits an event indicating new value counter.component.ts
import { Component, Input, Output, EventEmitter }
from '@angular/core';

@Component({
selector: 'counter',
template: `<p><ng-content></ng-content>
Count: {{ count }} -
<button (click)="increment()">Increment</button></p>`
})
export class CounterComponent {
@Input() count: number = 0;
@Output() countChanged = new EventEmitter<number>();

increment() {
this.count++;
this.countChanged.emit(this.count);
}
}
Component Event Example

• Parent component
app.component.ts
@Component( {
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
num: number = 0;
childValue: string = 'empty';

onCountChanged( n: number ) {
this.childValue = `From Child ${n}`;
}
app.component.html
}
<counter [(count)]="num"
(countChanged)="onCountChanged($event)">
Number:
</counter>
<ul>
<li>num: {{num}}</li>
<li>Child Value: {{childValue}}</li>
</ul>
Component Event Example

• The parent is notified when the child makes a change


DOM Events

• Can also handle DOM events

• Structure of elements in a page


– elements are nested inside other elements

<body>
<div>
<p>
<ul>
<li>...</li>

• What happens when an DOM event occurs?


– one (or more) of the elements could be responsible for handling event
– when the list item is clicked – what should respond?
DOM Level 2 Event Propagation

• Two alternative approaches


capturing bubbling
<table>
<tr>
<td>

• Event capture
– event is first given to most all-encompassing element
– then successively more specific ones
• Event bubbling
– event is first sent to most specific element
– then successively less specific ones
• Not all browsers used to support capture
– default is therefore bubbling
Handling DOM Events

• Define event handler method in component


– note receive event object in method
@Component(...)
class MyComponent {
clicked(event) {
console.log(event);
}
}

– can prevent further process of event using event.preventDefault();


• Indicate method to call on event in template
– e.g. clicking a button
– note use parenthesis notation in templates
<button (click)="clicked()">Click</button>

– to capture event object pass $event


<button (click)="clicked($event)">Click</button>
Child Routes

• Child route only accessible within context of (parent) route


– parent view presents product information
– tabs within this allow details, pictures, specifications to be viewed
– but these tabs only make sense in the content of the select product
– each tab is a child of the parent produce details view

• If the user selects product with id 3


– overview view is relative to id 3
– details view is relative to id 3 etc.
– overview and details are child routes of the product route

• Child routes defined within Route Definition object


– using the children property
Child Routes

• Two child routes defined relative to 'comp-two' route


– can define a default path
– each child route has a name and the target for the path

export const routes: Routes = [


{ path: '', redirectTo: 'comp-one', pathMatch: 'full' },
{ path: 'comp-one', component: ComponentOne },
{ path: 'comp-two', component: ComponentTwo,
children: [
{ path: '', redirectTo: 'child-one',
pathMatch: 'full' },
{ path: 'child-one', component: ChildOne },
{ path: 'child-two', component: ChildTwo }
]
}
];
Child Routes Example

• Application with child routes

App
Component

top level routes

Component Component
One Two

child routes

Child One Child Two


Child Routes Example

• Root Component unaware of child routes


import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {}

app.component.html
<nav>
<a routerLink="/comp-one">Component One</a>
<a routerLink="/comp-two">Component Two</a>
</nav>
<h2>Selected View</h2>
<div style="border: 2px solid blue; padding: 1rem">
<router-outlet></router-outlet>
</div>
Child Routes Example

• Component Two
– references child routes
– has router-output in template for location of child content
– children are encapsulated into the component

@Component( {
selector: 'component-two',
templateUrl: './component-two.html'
})
export class ComponentTwo {}

component-two.html
<h3>Component Two</h3>
<a [routerLink]="['child-one']">Child One</a>
<a [routerLink]="['child-two']">Child Two</a>
<div style="border: 2px solid red">
<h4>Component Two: Child View</h4>
<router-outlet></router-outlet>
</div>
Child Routes Example

• Child components defined as usual


import { Component } from '@angular/core';

@Component({
selector: 'child-one',
template: 'Child One'
})
export class ChildOne {
}
import { Component } from '@angular/core';

@Component({
selector: 'child-two',
template: 'Child Two'
})
export class ChildTwo {
}
Child Routes Example

• The running application


Child Routes and Parameters

• Child routes can have parameters


– just as top-level routes have parameters
– specified in Route Definition Object

export const routes: Routes = [


{ path: '', redirectTo: 'comp-one', pathMatch: 'full' },
{ path: 'comp-one', component: ComponentOne },
{ path: 'comp-two', component: ComponentTwo,
children: [
{ path: '', redirectTo: 'child-one',
pathMatch: 'full' },
{ path: 'child-one', component: ChildOne },
{ path: 'child-two/:num', component: ChildTwo }
]
}
];
Child Routes and Parameters

• Child component access route parameter information


– in same way as top-level route parameters
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'child-two',
template: 'Child Two (num: {{ num }})'
})
export class ChildTwo {
private sub: any;
private num;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.num = +params['num'];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
Child Routes and Parameters

• Parent can set value for child route parameter

@Component( {
selector: 'component-two',
templateUrl: './component-two.html'
})
export class ComponentTwo {}

component-two.html
<h3>Component Two</h3>
<a [routerLink]="['child-one']">Child One</a>
<a [routerLink]="['child-two', 987]">Child Two</a>
<div style="border: 2px solid red">
<h4>Component Two: Child View</h4>
<router-outlet></router-outlet>
</div>
Accessing Parent Path Parameters

• Child can access parent's route parameters


– accessible from ActivatedRoute object's parent property
– provides access to parent property
– parent property allows access to params

• Handle same was as other parameters


– can subscribe to property
– notified when value changes

• Parent unaware child is access property


– parent can still subscribe to params as usual
Accessing Parent Path Parameters

• Routes array defined paths with ids


– comp-two path has :id
• Can access this from child components
– e.g. in ChildOne from 'child-one' route

export const routes: Routes = [


{ path: '', redirectTo: 'comp-one', pathMatch: 'full' },
{ path: 'comp-one', component: ComponentOne },
{ path: 'comp-two/:id', component: ComponentTwo,
children: [
{ path: '', redirectTo: 'child-one',
pathMatch: 'full' },
{ path: 'child-one', component: ChildOne },
{ path: 'child-two', component: ChildTwo }
]
}
];
Accessing Parent Parameter

• ChildOne access :id from parent path


– stored in parentRouteId used in view template

import { Component } from '@angular/core';


import { ActivatedRoute } from '@angular/router';

@Component({
selector: 'child-one',
template: `
Child One, reading parent route param.
<b><code>Parent ID: {{ parentRouteId }}</code></b>
`
})
export class ChildOne {
private sub: any;
private parentRouteId: number;

constructor(private route: ActivatedRoute) {}


Accessing Parent Parameter

ngOnInit() {
this.sub = this.route.parent.params.subscribe(params =>
{
this.parentRouteId = +params["id"];
});
}

ngOnDestroy() {
this.sub.unsubscribe();
}
}
Controlling Access to Routes

• Route guards
– can be used to control whether a user can
– navigate to or from
– a given route

• For example,
– a route is only available once user has logged in
– or they are unable to navigate away from a route until they have
accepted terms & conditions etc.

• Three interfaces used to define guards


– CanActivate used to allow a route to be followed
– CanDeactivate defines class used to say if route can be deactivated
– CanActivateChild for guarding child routes
– both defined in @angular/route
Controlling Access to Routes

• CanActivate interface

canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) :
Observable<boolean>|Promise<boolean>|boolean

• CanDeactivate interface

canDeactivate(component: T,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot) :

Observable<boolean>|Promise<boolean>|boolean
}
Controlling Access to Routes

• Registering Route Guards


– route definition object
– defines canActivate and canDeactivate properties
– canActivate defines a guard that decides if route can be activated
– canDeactivate defines a guard to check if route can be deactivated

export const routes: Routes = [


{ path: '', redirectTo: 'comp-one', pathMatch: 'full' },
{ path: 'comp-one', component: ComponentOne },
{ path: 'comp-two', component: ComponentTwo,
canActivate: [ActivateGuard],
canDeactivate: [DeactivateGuard]
}
];

– ActivateGuard will be checked by router before activating comp-two


– DeactivateGuard will be checked before leaving route
Implementing Access Control Guards

• Application structure
App Module

providers

App
Component

comp-one comp-two

ActivateGuard

Component Component
One Two

DeactivateGuard
passed as parameter
Implementing Access Control Guards

• Must implement appropriate interfaces


– if injected into components must be marked as @Injectable()
– common as application typically determines activation

import { CanActivate } from '@angular/router';


import { Injectable } from '@angular/core';

@Injectable()
export class ActivateGuard implements CanActivate {
private flag: boolean = false;
canActivate() {
if (!this.flag) {
alert('Activation blocked');
return false;
}
return true;
}
setCanActivate(flag) {
this.flag = flag;
}
}
Implementing Access Control Guards

• DeactivateGuard references CompnentTwo


– this is injected by Angular & used to check if route can be deactivated

import { CanDeactivate } from '@angular/router';


import { ComponentTwo } from './component-two';

export class DeactivateGuard


implements CanDeactivate<ComponentTwo> {

canDeactivate(component: ComponentTwo) {
let flag = component.canDeactivate();
if (!flag) {
alert('Deactivation blocked');
return false;
}
return true;
}

}
Implementing Access Control Guards

• Module declaration
– guards listed in providers property
– ensures they are scoped to the module
– act like a service for routing decisions

@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routes)
],
declarations: [AppComponent, ComponentOne, ComponentTwo ],
providers: [ ActivateGuard, DeactivateGuard ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
Implementing Access Control Guards

• AppComponent
– has activate guard injected to it
– can then set flag on ActivateGuard as appropriate

import { Component } from '@angular/core';


import { ActivateGuard } from './activate-guard';

@Component( {
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor( private activateGuard: ActivateGuard ) { }

checkboxChanged( canActivate ) {
// Update guard when checkbox checked.
this.activateGuard.setCanActivate( canActivate );
}
}
Implementing Access Control Guards

• ComponentTwo maintains a flag


– used to check whether the route can be deactivated

import { Component } from '@angular/core';


@Component({
selector: 'component-two',
templateUrl: './component-two.html'
})
export class ComponentTwo {
private checked = false;

canDeactivate() {
return this.checked;
}

checkboxChanged(checked) {
this.checked = checked;
}
}
Implementing Access Control Guards

• Component two guarded


Implementing Access Control Guards

• Deactivation blocked
Secondary Routes

• Multiple independent routes in a single application

• Each component has a primary route


– zero or more auxiliary outlets
– auxiliary outlets uniquely named within component

• Secondary routes
– are independent routes
– can have own child routes
– may also have own auxiliary routes
– can have own route-params
– have own history stack

• The <router-outlet> has an optional name property


– referenced in routerLink
Secondary Routes Example

• AppComponent
– defines 'footer' router-outlet in view template
– plus routerLink for named outlet and comp-aux path
app.component.html
<div>
<profile #profile></profile>
<a [routerLink]="['/comp-one']">Component One</a>
<a [routerLink]="['/comp-two']">Component Two</a>
<a [routerLink]=
"[{ outlets: { 'footer': ['comp-aux'] } }]">Component
Aux</a>
<p><b>Outlet:</b></p>
<div style="border: 2px solid green">
<router-outlet></router-outlet>
</div>
<p><b>Sidebar Outlet:</b>
<div style="border: 2px solid blue">
<router-outlet name="footer"></router-outlet>
</div>
Secondary Routes Example

• Routes define auxiliary route object


– with a named outlet
app.component.ts
export const routes: Routes = [
{ path: '', redirectTo: 'comp-one', pathMatch: 'full' },
{ path: 'comp-one', component: ComponentOne },
{ path: 'comp-two', component: ComponentTwo },
{ path: 'comp-aux', component: ComponentAux, outlet: 'footer'
}
];

• Module must declare the auxiliary component


app.module.ts
@NgModule( {
...
declarations: [
AppComponent, ComponentOne, ComponentTwo, ComponentAux ],
})
export class AppModule {
Secondary Routes Example

• The secondary display


Change Detection

• Very different to AngularJS 1.x approach


– based on watchers checked each time the digest-cycle ran
– bindings could be two-way
– could result in a lot of checking
– could represent a performance bottleneck

• Angular approach
– all flow is unidirectional
– two-way event binding is treated as 2 one-way bindings
– application code is responsible for updating models
– Angular only responsible for reflecting changes
Two Phases

• Separate phases for


– updating the application model
– reflecting state of model in the views

Two Phases

Change
Event Phase Detection
Phases

Event Model Property View


Bindings Updates Bindings Updates
Change Detector Classes

• Created at runtime by Angular


– have AppComponent
– will get an AppComponent_ChangeDetector

• Detector object has reference back to component

• Determines which model properties


– used in the components view template
– have changed
– since the change detection process ran

• Conceptually change detector


– records component property values
– next time it runs it compares current values with those recorded
– if values have changed updated the component
Change Detection

• Angular applications built from hierarchy of components

App
Component

Menu Admin ToDoList


Component Component Component

ToDo ToDo
Component Component
Change Detection

• Angular applications built from hierarchy of components


change detector
traverses each node App
once from the root Component

Menu Admin ToDoList


Component Component Component

ToDo ToDo
Component Component
Change Detection

• Angular applications built from hierarchy of components

App parent is always


checked before
Component
child components

Menu Admin ToDoList


Component Component Component

ToDo ToDo
Component Component
Change Detection

• Angular applications built from hierarchy of components

App
Component
1 3
2

Menu Admin ToDoList


Component Component Component
4 5

evaluation is depth ToDo ToDo


first
Component Component
How Change Detection Works

• Every component has a change detector


App App_ChangeDetector
Component

Admin_Change ToDoList_ChangeDetector
Admin ToDoList
Detector
Component Component

ToDo ToDo ToDo_ ToDo_


Component Component ChangeDetector ChangeDetector
How Change Detection Works

• Every component has a change detector


App App_ChangeDetector
Component

todos:List<ToDo>

Admin_Change ToDoList_ChangeDetector
Admin ToDoList
Detector
Component Component

todo: ToDo todo: ToDo

ToDo ToDo
Component Component {{todo.text}} {{todo.text}}
todo.done todo.done

ToDo_ ToDo_
ChangeDetector ChangeDetector
Change Detection and Two-Way Bindings

• No change detection mechanism for two-way bindings


– implemented as two one-way bindings
– via NgModel and the banana syntax e.g. [(ngModel)]-"property"
– really a data binding from model to view
– an event binding view to the component

Event Binding
View Template Component
Property Binding

• Allows change detection graph to be a directed tree


– no cycles
– promotes more performant system
– more predictable behavior
– compared to AngularJS 1.x approach
Change Detection Strategies

• Every component has a change detection strategy

• There are two change detection strategies available


– Default
– OnPush

• Default
– change detector's mode will be set to CheckAlways

• OnPush
– change detector's mode will be set to CheckOnce

• Defined on the @Component changeDetection property


– values defined by the ChangeDetectionStrategy class
Change Detection Strategies

• Setting the change detection strategy on a component

app.component.ts
import { Component, ChangeDetectionStrategy }
from '@angular/core';

@Component({
selector: 'app-root',
template: `<h2>Parent</h2>
<hr />
<child-comp></child-comp>
<hr />
`,
changeDetection: ChangeDetectionStrategy.Default
})
export class AppComponent { }
Change Detection Strategy

• Default Strategy
– will traverse all the components in the tree to check for changes

• Performance Impact
– checking all components can be quite costly

• OnPush strategy
– used to indicate that a component only relies on its inputs
– any other component passed to it is considered immutable
– children do not need to keep being checked; they are immutable
– can cut out portions of the dependency tree
Change Detection Strategies

• If the ToDoList is marked as using OnPush


App App_ChangeDetector
Component

OnPush
Admin_Change ToDoList_ChangeDetector
Admin ToDoList
Detector
Component Component

ToDo ToDo ToDo_ ToDo_


Component Component ChangeDetector ChangeDetector

can skip change


detection on these
components
Zones

• Zones provide an execution context


– that encapsulates and intercepts asynchronous activities
– can think of it as thread local storage for JavaScript
– provided by Zone.js

• Used by Angular to track the start and completion


– of asynchronous operations
– but also supports change detection

• Angular has a global zone


– defined by NgZone service with extensions

• NgZone service
– makes several observables available
– useful for determining state of Angular's Zone
In the Zone

• NgZone exposes set of Observables


– to determine status / stability of Angular Zone

• Observables available
• onUnstable – notifies code has entered & executing in zone
• onMicrotaskEmpty – indicates microtasks are queued for execution
• onStable – notifies when the last onMicroTaskEmpty has run
• onError – notifies when an error has occurred

• To subscribe inject NgZone


• into components / services etc.

• Can run code inside or outside of zone


• code outside of Angular Zone
• not part of change detection cycle
In the Zone

import { Injectable, NgZone } from '@angular/core';

@Injectable()
export class ZoneWatchService {
constructor(private ngZone: NgZone) {
this.ngZone.onStable.subscribe(this.onZoneStable);
this.ngZone.onUnstable.subscribe(this.onZoneUnstable);
this.ngZone.onError.subscribe(this.onZoneError);
}

onZoneStable() {
console.log('We are stable');
}

onZoneUnstable() {
console.log('We are unstable');
}

onZoneError(error) {
console.error(error.toString());
}
}
In the Zone

• Console output from application


Zones and Change Detection

• All asynchronous code within Angular's zone


– can trigger change detection processing

• Can execute code outside of Angular's Zone


– avoids change detection overhead
– but of course not monitored for change detection effects

• Supported by
– ngZone.runOutsideAngular

ngZone.runOutsideAngular(this.<someMethod>.bind(this));
Executing Outside the Zone

• Service to run behavior inside or outside Zone


executor.service.ts
import { Injectable, NgZone } from '@angular/core';

@Injectable()
export class ExecutorService {
private interval: any;

constructor( private ngZone: NgZone ) {


this.ngZone.onStable.subscribe(
this.onZoneStable.bind( this ) );
this.ngZone.onUnstable.subscribe(
this.onZoneUnstable.bind( this ) );
}
onZoneStable() {
console.log( 'Angular zone is stable' );
}

onZoneUnstable() {
console.log( 'Angular zone is unstable' );
}
Executing Outside the Zone

start( outside: boolean ) {


if ( outside ) {
this.ngZone.runOutsideAngular(
this.createInterval.bind(this) );
} else {
this.createInterval();
}
}

createInterval() {
clearInterval( this.interval );
this.interval =
setInterval(this.logRandom.bind(this), 1000);
}

logRandom() {
console.log('Calc: ' + Math.floor(Math.random() *
2000));
}

}
Executing Outside the Zone

• The AppComponent using the Service


app.component.ts
import { Component } from '@angular/core';
import { ExecutorService } from './executor.service';

@Component({
selector: 'app-root',
template: `
<button (click)="handleClick(false)">Inside Angular Zone
</button>
<button (click)="handleClick(true)">Outside Angular Zone
</button>
`
})
export class AppComponent {
constructor(private service: ExecutorService) { }

handleClick(outside: boolean) {
this.service.start(outside);
}
}
Executing Outside the Zone

• Running the application


Additional Resources

• Angular API Docs: Change Detection Strategy


– https://angular.io/docs/ts/latest/api/core/index/ChangeDetectionStrate
gy-enum.html
• Last Guide For Angular Change Detection You'll Ever
Need
– https://www.mokkapps.de/blog/the-last-guide-for-angular-change-
detection-you-will-ever-need
• Change Detection in Angular 2
– https://vsavkin.com/change-detection-in-angular-2-
4f216b855d4c#.it783qd8q
• Two Phases of Angular 2 Applications
– https://vsavkin.com/two-phases-of-angular-2-applications-
fda2517604be#.4mgodvk0z
• Understanding Zones
– https://blog.thoughtram.io/angular/2016/01/22/understanding-
zones.html
What is Angular Material?

• A third-party package
– used on Angular projects to facilitate the development process
• Provides reutilization of common components e.g.:
– cards
– inputs
– data tables

• For a full reference of components with examples


– https://material.angular.io/

• With Angular
– entire app is a composition of components
• Leveraging with Angular Material
– provides out-of-the-box styled components
– instead of building and styling components from the group up
Angular Material Components

• 6 categories of components:

– form controls

– navigation

– layout

– buttons & Indicators

– popups & Modals

– data table
Form Controls
Navigation
Layout Controls
Buttons and Indicators
Popups and Modals
Data Table
The Component Development Kit (CDK)

• A set of tools
– that implement common interaction patterns
– whilst being unopinionated about their presentation

• Represents an abstraction of the core functionalities


– found in the Angular Material library
– without any styling specific to Material Design

• 3 categories
– common behaviours
– components
– testing

• Documentation is at:
– https://material.angular.io/cdk/categories
Common Behaviors
Components
Testing
Example Application

• A service providing names of Shakespeare plays

• A welcome screen giving basic information

• A component to display the names of the plays


– and the ability to delete plays in the service

• Routes defined for these 2 components

• A dialog box for adding new plays to the service


Setting up the Project

• Firstly, create the project


– and add a number of Node.js modules:

$ ng new angular-material

$ cd angular-material

$ npm install @angular/material @angular/cdk


Angular Material Theme

• After installing Angular Material


– configure a theme that defines what colors will be used
– by the Angular Material components

– add selected theme to src/styles.css


– e.g.:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
Adding Angular Material Gesture

• Dependencies exist on HammerJS


– to capture touch gestures
– used by sliders and tooltips, for example

• Add the module:


$ npm install hammerjs

• Then add the following at the top of src/main.ts:

import 'hammerjs';
Adding Material Icons

• To add support for the large collection of Material Icons


– update src/index.html as follows:

<!doctype html>

<html lang="en">

<head>
<!-- ... other tags ... -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons”
rel="stylesheet">
</head>

<!-- ... body and app root ... -->

</html>
Importing Material Components

• To import the Material Components


– create a new module

import {NgModule} from '@angular/core';

@NgModule({
imports: [],
exports: []
})

export class MaterialModule {}


Importing Material Components

• Need to import and configure the main module


– update ./src/app/app.module.ts

// ..other modules as already defined

import {BrowserAnimationsModule} from


'@angular/platform-browser/animations';
import {MaterialModule} from './material.module';

@NgModule({
// declarations property
imports: [
BrowserModule,
BrowserAnimationsModule,
MaterialModule,
],

// providers and bootstrap properties


})

export class AppModule { }


Angular Material Sidenav

import {NgModule} from '@angular/core';

import { MatSidenavModule } from '@angular/material/sidenav';


import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';

@NgModule({
imports: [
MatSidenavModule,
MatToolbarModule,
MatIconModule,
MatListModule,
],
exports: [
MatSidenavModule,
MatToolbarModule,
MatIconModule,
MatListModule,
]
})
export class MaterialModule {}
Angular Material and Flexbox

• Using the Flexlayout schema


– makes the layour of the Angular application easier
– uses an Angular directive called fxFlex

• To add support, add the following modules:

$ npm install @angular/flex-layout rxjs


Angular Material and Flexbox

• The module then needs to be imported and configured


– done in the src/app.module.ts file:

// ... other imports

import {FlexLayoutModule} from '@angular/flex-layout';

@NgModule({
// ... declarations property

imports: [
// .. other imports
FlexLayoutModule,
],

// ... providers and bootstrap properties


})

export class AppModule { }


More Angular Material Components

• Adding2 more components to the application:


– a component with a welcome message
– a component for viewing the plays

$ ng g c welcome --module app.module

$ ng g c dashboard --module app.module

– note the use of --module app.module

• Add some content to welcome.component.html

<div style="text-align:center">
<h1>The Play Management System</h1>
<p>
This is a tool to keep track of (Shakespeare) plays
</p>
</div>
Creating Routes

• Adding routes to app-routing.module.ts


import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { WelcomeComponent } from './welcome/welcome.component';
import { DashboardComponent } from './dashboard/dashboard.component';

const routes: Routes = [


{path: '', component: WelcomeComponent},
{path: 'dashboard', component: DashboardComponent}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

• Also need to update app.component.html


The Welcome Component
Creating Routes

• Update app.module.ts
– if not already there

/// ... previous imports

import { AppRoutingModule } from './app-routing.module’;

// .. further imports
@NgModule({
/// declarations

imports: [
// .. other imports
AppRoutingModule,
],

// .. providers and bootstrap


})
export class AppModule { }
The Play Service

• Define the export class Play {


src/app/play.ts
structure of a play playid: number;
title: string;

constructor(i: number, t: string) {


this.playid = i;
this.title = t;
}
}

• And create the $ ng g s play/play --module app.module


service
The Play Service

• Implementing the play service


src/app/play/play.service.ts
import { Injectable } from '@angular/core';
import {Observable, of} from 'rxjs';
import { Play } from 'src/play';

@Injectable()
export class PlayService {

PLAYS: Play[] = [
{ playid: 0, title: 'Hamlet' },
{ playid: 1, title: 'The Tempest' },
{ playid: 2, title: 'Othello' },
{ playid: 3, title: 'Macbeth' },
{ playid: 4, title: 'Henry III' }
];

constructor() {}

getPlays(): Observable<Play[]> {
return of<Play[]>(this.PLAYS);
}
...
The Play Service

src/app/play/play.service.ts
...
addPlay(play) {
this.PLAYS.push(play);
}

deletePlay(index) {
this.PLAYS = [...this.PLAYS.slice(0, index),
...this.PLAYS.slice(index + 1, this.PLAYS.length+1)];
// a hack to update the playid field!!!
let x=0;
for (let i in this.PLAYS) {
this.PLAYS[x].playid = x;
x++;
}
}

playLength() {
return this.PLAYS.length;
}
}
Updating the Dashboard
src/app/dashboard/dashboard.component.ts
import {Component} from '@angular/core';
import {PlayService} from '../play/play.service';
import {Play} from 'src/play';
import {DataSource} from '@angular/cdk/table';
import {Observable} from 'rxjs';
import {MatDialog} from '@angular/material/dialog';
import { PlayDialogComponent } from
'../play-dialog/play-dialog.component';

@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})

export class DashboardComponent {


constructor(public dialog: MatDialog, private playService:
PlayService) { }

...
Updating the Dashboard
src/app/dashboard/dashboard.component.ts
...

displayedColumns = ['playid', 'title', 'delete'];

playSource = new PlayDataSource(this.playService);

deletePlay(id) {
this.playService.deletePlay(id);
this.playSource = new PlayDataSource(this.playService);
}

openDialog(): void {
let dialogRef = this.dialog.open(PlayDialogComponent, {
width: '600px',
data: 'Add Play'
});
dialogRef.componentInstance.event.subscribe((result) => {
this.playService.addPlay(result.play);
this.playSource = new PlayDataSource(this.playService);
});
}
}
...
Updating the Dashboard
src/app/dashboard/dashboard.component.ts
...

export class PlayDataSource extends DataSource<any> {

constructor(private playService: PlayService) {


super();
}

connect(): Observable<Play[]> {
return this.playService.getPlays();

disconnect() {
}

}
Rendering the Dashboard
src/app/dashboard/dashboard.component.html
<div class="container">
<div fxLayout="row" fxLayoutAlign="center center"
class="content">
<mat-card class="card" >
<mat-card-title fxLayout.gt-xs="row” fxLayout.xs="column">
<h3>Plays</h3>
</mat-card-title>

<mat-card-content>
<div class="example-container mat-elevation-z8">
<mat-table #table [dataSource]="playSource">

<ng-container matColumnDef="playid">
<mat-header-cell *matHeaderCellDef> Play ID
</mat-header-cell>
<mat-cell *matCellDef="let element">
{{element.playid}}
</mat-cell>
</ng-container>

...
Rendering the Dashboard
src/app/dashboard/dashboard.component.html
...

<mat-header-row *matHeaderRowDef="displayedColumns">
</mat-header-row>

<mat-row *matRowDef="let row; columns:displayedColumns;">


</mat-row>

</mat-table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
The Dashboard.
The Dashboard.
Enabling Data Input

• Add the button to open the dialog


...
<button button="submit" mat-raised-button color="primary"
(click)="openDialog()">
<mat-icon>add</mat-icon> Add Post
</button>

• Angular Material buttons are just native buttons


– with Material styling
Enabling Data Input

• Add the button to open the dialog

• Angular Material buttons are just native buttons


– with Material styling
The Dialog Component

• A new component is created for the dialog:

$ ng g c play-dialog --module app.module


The Dialog Component

• The HTML for the Dialog

<h1 mat-dialog-title>{{data}}</h1>
<div mat-dialog-content>
<form class="example-form" (ngSubmit)="onSubmit()">
<mat-form-field>
<input matInput placeholder="Play Title" type="text" required
[(ngModel)]="playPost.title" name="title">
</mat-form-field>
<button mat-raised-button type="submit" color="primary">
Save
</button>
</form>
</div>
<div mat-dialog-actions>
<button mat-raised-button class="close" (click)="onNoClick()"
color="warn">Cancel</button>
</div>
The Dialog Component

• The component implementation


import {Component, EventEmitter, Inject} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {PlayService} from '../play/play.service';

@Component({
selector: 'app-play-dialog',
templateUrl: './play-dialog.component.html',
styleUrls: ['./play-dialog.component.css']
})
export class PlayDialogComponent {
playPost = {
title: '',
playid: 0
};

public event: EventEmitter<any> = new EventEmitter();

...
The Dialog Component

• The component implementation


...

constructor(
public dialogRef: MatDialogRef<PlayDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
public playService: PlayService) { }

onNoClick(): void {
this.dialogRef.close();
}

onSubmit(): void {
this.playPost.playid = this.playService.playLength();
this.event.emit({play: this.playPost});
this.dialogRef.close();
}

}
The Dialog Component

• After opening the dialog


The Dialog Component

• After the new play has been added


Best Practices

• Keep current with the latest Angular library releases


– the Angular libraries are updated regulalry
– and these updates may fix security defects discovered in previous
versions.
– check the Angular change log for security-related updates
– use npm outdated

• Don't modify your copy of Angular


– private, customized versions of Angular fall behind the current version
– may not include important security fixes and enhancements
– share your Angular improvements with the community
– and make a pull request

• Angular APIs marked “Security Risk” should be avoided


– see the API documentation
Preventing Cross-Site scripting (XSS)

• Cross-site scripting (XSS)


– enables attackers to inject malicious code into web pages
– used to steal user data (in particular, login data)
– or perform actions to impersonate the user
– one of the most common attacks on the web

• Examples of common attacks include:


– blog sites where people can enter text
– users put <script> tags into the message they write
– code then run on the browser if not caught
Preventing Cross-Site scripting (XSS)

• To block XSS attacks


– prevent malicious code from entering the DOM

• For example
– if attackers can trick you into inserting a <script> tag in the DOM
– they can run arbitrary code on your website
– not only limited to <script> tags
Angular’s Cross-Site Scripting Security Model

• To systematically block XSS bugs


– Angular treats all values as untrusted by default

• When a value is inserted into the DOM


– from a template, via property, attribute, style, class binding ...
– Angular sanitizes and escapes untrusted values
Angular’s Cross-Site Scripting Security Model

• Angular templates are the same as executable code

• HTML, attributes, and binding expressions


– in templates are trusted to be safe
– but not the values bound

• Never generate template source code


– by concatenating user input and templates

• Use the offline template compiler


– also known as template injection
Sanitization and Security Contexts

• Sanitization is the inspection of an untrusted value


• turning it into a value that's safe to insert into the DOM

• In many cases, sanitization doesn't change a value at all


– depends on context
– a value that's harmless in CSS is potentially dangerous in a URL
Sanitization and Security Contexts

• Angular defines the following security contexts:

• HTML
– used when interpreting a value as HTML
– for example, when binding to innerHtml

• Style
– used when binding CSS into the style property

• URL
– used for URL properties, such as <a href>

• Resource URL
– a URL that will be loaded and executed as code
– for example, in <script src>
Sanitization Example

• Example html template

interpolating into an element's content

<h3>Binding innerHTML</h3>

<p>Bound value:</p>
<p class="e2e-inner-html-interpolated">{{htmlSnippet}}</p>

<p>Result of binding to innerHTML:</p>


<p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>

binding to innerHTML property of an element


Sanitization Example

• Example component code

export class InnerHtmlBindingComponent {


// For example, a user/attacker-controlled value from a URL.
htmlSnippet = 'Template <script>alert("0wned")
</script> <b>Syntax</b>';
}
Sanitization Example
Direct Use of the DOM

• Built-in browser DOM APIs


– don't automatically protect you from security vulnerabilities
• Many APIs contain unsafe methods, for example
– document
– the node available through ElementRef

• If interacting with other libraries


– any that manipulate the DOM
– don’t have same automatic sanitization as with Angular interpolations
• Avoid directly interacting with the DOM
– instead use Angular templates where possible
Explicit Sanitization Calls

• For cases where this is direct DOM access unavoidable


– use the built-in Angular sanitization functions

• Sanitize untrusted values


– with the DomSanitizer.sanitize method
– and the appropriate SecurityContext

• Also accepts values that were marked as trusted


– by using the bypassSecurityTrust... functions
– will not sanitize them
Content Security Policy

• A defense-in-depth technique to prevent XSS

• To enable:
– requires configuration of the web server
– to return an appropriate Content-Security-Policy HTTP header
Use the Offline Template Compiler

• The offline template compiler


– prevents a whole class of vulnerabilities called template injection
– greatly improves application performance

• Use in production deployments


– don't dynamically generate templates

• Angular trusts template code


– generating templates, in particular templates containing user data
– circumvents Angular's built-in protections
Server-side XSS protection

• HTML constructed on the server


– vulnerable to injection attacks

• Injecting template code into an Angular application


– same as injecting executable code into the application
– gives the attacker full control over the application

• To prevent this
– use a templating language that automatically escapes values
– prevents XSS vulnerabilities on the server

• Don't generate Angular templates on the server side


– by using a templating language
– carries a high risk of introducing template-injection vulnerabilities
Trusting Safe Values

• Sometimes applications genuinely need to


– include executable code
– display an <iframe> from some URL
– construct potentially dangerous URLs
• To prevent automatic sanitization
– tell Angular that you inspected a value
– checked how it was generated
– and made sure it will always be secure
• To mark a value as trusted
– inject DomSanitizer and call one of the following methods:
– bypassSecurityTrustHtml
– bypassSecurityTrustScript
– bypassSecurityTrustStyle
– bypassSecurityTrustUrl
– bypassSecurityTrustResourceUrl
Trusting Safe Values

• For the following template


– that needs to bind a URL to a javascript:alert(...) call:

<h4>An untrusted URL:</h4>

<p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me</a></p>

<h4>A trusted URL:</h4>

<p><a class="e2e-trusted-url" [href]="trustedUrl">Click me</a></p>


Trusting Safe Values

• Normally, Angular automatically sanitizes the URL


– and disables the dangerous code

• To prevent this
– mark the URL value as a trusted URL
– using the bypassSecurityTrustUrl call:

constructor(private sanitizer: DomSanitizer) {


// javascript: URLs are dangerous if attacker controlled.
// Angular sanitizes them in data binding, but you can
// explicitly tell Angular to trust this value:
this.dangerousUrl = 'javascript:alert("Hi there")';
this.trustedUrl =
sanitizer.bypassSecurityTrustUrl(this.dangerousUrl);
Trusting Safe Values

• The untrusted URL is sanitized, as expected:


Trusting Safe Values

• The trusted URL is permitted:


Trusting Safe Values

• To convert user input into a trusted value


– use a controller method
template
<h4>Resource URL:</h4>
<p>Showing: {{dangerousVideoUrl}}</p>
<p>Trusted:</p>
<iframe class="e2e-iframe-trusted-src" width="640"
height="390" [src]="videoUrl"></iframe>
<p>Untrusted:</p>
<iframe class="e2e-iframe-untrusted-src" width="640"
height="390" [src]="dangerousVideoUrl"></iframe>

updateVideoUrl(id: string) { component


// Appending an ID to a YouTube URL is safe. Always make sure
// to construct SafeValue objects as close as possible to the
// input data so that it's easier to check if the value is safe.
this. dangerousVideoUrl = 'https://www.youtube.com/embed/' + id;
this.videoUrl =
this.sanitizer.bypassSecurityTrustResourceUrl
(this. dangerousVideoUrl);
}
HTTP-Level Vulnerabilities

• Support to help prevent two common HTTP vulnerabilities


– cross-site request forgery (CSRF or XSRF)
– cross-site script inclusion (XSSI)

• Both must be mitigated primarily on the server side


– Angular provides helpers to make integration on the client side easier
Cross-site request forgery

• In a cross-site request forgery (CSRF or XSRF)


– attacker tricks the user into visiting a different web page
– with malignant code that secretly sends a malicious request
– to the application's web server

• To prevent this
– application must ensure user requests originate from real application
– not from a different site
– server and client must cooperate to thwart this attack

• Common anti-XSRF technique


– application server sends a randomly generated authentication toke
– in a cookie

• Angular's HttpClient has built-in support


– for the client-side half of this technique
Cross-site script inclusion (XSSI)

• Also known as JSON vulnerability


– can allow an attacker's website to read data from a JSON API

• The attack works on older browsers


– overrides native JavaScript object constructors
– then includes an API URL using a <script> tag

• This attack is only successful


– if the returned JSON is executable as JavaScript
• Servers can prevent an attack
– by prefixing all JSON responses to make them non-executable
– using the well-known string ")]}',\n".
• Angular's HttpClient library recognizes this convention
– automatically strips the string ")]}',\n" from all responses
– before further parsing
Summary

• Angular app must follow the same security principles


– as regular web applications
– and must be audited as such

• APIs that should be audited in a security review


– such as the bypassSecurityTrust methods
– are marked in the documentation as security sensitive

• Plenty of external documentation and resources:


– use OWASP
Protractor Introduction

• Protractor is an end-to-end test framework


– for Angular and AngularJS applications
– runs tests against your application running in a real browser
– interacting with it as a user would

• Built on top of Selenium WebDriverJS


– uses native events and browser-specific drivers
– to interact with your application as a user would

• Supports Angular-specific locator strategies


– allows testing of Angular-specific elements
– without any setup effort
Protractor vs. Jasmine

• Angular-CLI ships with two frameworks for testing:


– unit tests using Jasmine and Karma
– used to test the logic of the components and services

– end-to-end tests using Protractor


– used to ensure that the high-level functionality

• Can use Jasmine to write not just unit tests


– but basic UI tests also

• However, to test the front-end functionality


– from start to end
– Protractor is the way to go
Prerequisites for Using Protractor

• Need to have Node.js installed


– check the version of Node.js by running node --version
– check the compatibility notes in the Protractor README
– ensure version of Node.js is compatible with Protractor

• By default
– Protractor uses the Jasmine test framework for its testing interface

• Local standalone Selenium Server


– used to control browsers
– requires the Java Development Kit (JDK)
Setup

• Use npm to install Protractor globally:


npm install -g protractor

– will install two command line tools


– protractor and webdriver-manager
– run protractor --version to make sure it's working

• webdriver-manager is a helper tool


– to easily get an instance of a Selenium Server running
– use it to download the necessary binaries with:

webdriver-manager update
Setup

• Start up a server with:


webdriver-manager start

• This will start up a Selenium Server


– and will output a collection of info logs.
• Protractor tests will send requests to this server
– to control a local browser
– status of the server at http://localhost:4444/wd/hub
Configuring Protractor

• The default project template


– depends on two files to run the tests:

– the spec files that reside inside the e2e directory

– the configuration file (protractor.conf.js).

• We start with the configuration


Configuring Protractor
e2e/protractor.conf.js
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

const { SpecReporter } = require('jasmine-spec-reporter');

/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',

...
Configuring Protractor
e2e/protractor.conf.js
...

framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter
({ spec: { displayStacktrace: true } }));
}
};
A First Test

• Using a brand-new app


– created using ng new demo

– initial e2e test spec exists to test for welcome message

• 2 files used to define the tests


– app.e2e-spec.ts
– a typescript file specifying the tests

– app.po.ts
– a typescript file modelling a page, or part of an application
– e.g. text, headings, tables, buttons and links
– objects are imported into the spec file and their methods are invoked
A First Test
app.e2e-spec.ts
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';

describe('workspace-project App', () => {


let page: AppPage;

beforeEach(() => {page = new AppPage();});

it('should display welcome message', () => {


page.navigateTo();
expect(page.getTitleText()).toEqual('demo app is running!');
});

afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs()
.get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
A First Test
app.po.ts
import { browser, by, element } from 'protractor';

export class AppPage {


navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span’))
.getText() as Promise<string>;
}}
A First Test

$ ng e2e

...

** Angular Live Development Server is listening on localhost:4200,


open your browser on http://localhost:4200/ **
: Compiled successfully.
[00:53:34] I/launcher - Running 1 instances of WebDriver
[00:53:34] I/direct - Using ChromeDriver directly...
Jasmine started
[00:53:38] W/element - more than one element found for locator By
(css selector, app-root .content span) - the first result will be
used

workspace-project App
✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 0.668 sec.


[00:53:39] I/launcher - 0 instance(s) of WebDriver still running
[00:53:39] I/launcher - chrome #01 passed
A Second Set of Tests

• Added 2 new components:


– greeting (ng g c greeting)
– user (ng g c user)

• Updated app-component.html to:


<app-greeting></app-greeting>
<router-outlet></router-outlet>

• Configured routing:
...

const routes: Routes = [


{ path: '', pathMatch: 'full', redirectTo: 'app-greeting' },
{ path: 'user', component: UserComponent },
];

...
A Second Set of Tests

• App with empty path:


A Second Set of Tests

• App with user/ in the path:


A Second Set of Tests
app.po.ts
import { browser, by, element } from 'protractor';

export class AppPage {


navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}

navigateToUser(): Promise<unknown> {
return browser.get('user/') as Promise<unknown>;
}

getGreetingText(): Promise<string> {
return element(by.css('app-root app-greeting p’))
.getText() as Promise<string>;
}

getRouterOutletUserText(): Promise<string> {
return element(by.css('app-root app-user p’))
.getText() as Promise<string>;
}
}
A Second Set of Tests
app.e2e-spec.ts
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';

describe('workspace-project App', () => {


const page: AppPage = new AppPage();;

beforeEach(() => {
browser.get('/');
});

it('greeting component should display message', () => {


page.navigateTo();
expect(page.getGreetingText()).toEqual('greeting works!');
});

it('user component should display message', () => {


page.navigateToUser();
expect(page.getRouterOutletUserText()).toEqual('user works!');
});

afterEach(async () => {
...
A Second Set of Tests

$ ng e2e

...

** Angular Live Development Server is listening on localhost:4200,


open your browser on http://localhost:4200/ **
: Compiled successfully.
[03:00:47] I/launcher - Running 1 instances of WebDriver
[03:00:47] I/direct - Using ChromeDriver directly...
Jasmine started

workspace-project App
✓ greeting component should display message
✓ user component should display message

Executed 2 of 2 specs SUCCESS in 2 secs.


[03:00:56] I/launcher - 0 instance(s) of WebDriver still running
[03:00:56] I/launcher - chrome #01 passed
Using Locators

• The heart of end-to-end tests for webpages is:


– finding DOM elements
– interacting with them
– getting information about the current state of your application

• Protractor exports a global function element


– takes a Locator and will return an ElementFinder
– finds a single element
– if you need to manipulate multiple elements
– use the element.all function.

• The ElementFinder has a set of action methods


– such as click(), getText(), and sendKeys
– the core way to interact with an element
– and get information back from it
Locators

• Common locators:
// Find an element using a css selector
by.css('.myclass')

// Find an element with the given id


by.id('myid')

// Find an element using an input name selector


by.name('field_name')

• The locators are passed to the element function:


element(by.css('some-css'));
element(by.model('item.name'));
element(by.binding('item.name'));
Actions

• The element() function returns an ElementFinder

var el = element(locator);

// Click on the element.


el.click();

// Send keys to the element (usually an input).


el.sendKeys('my text');

// Clear the text in an element (usually an input).


el.clear();

// Get the value of an attribute, for example,


// get the value of an input
el.getAttribute('value');
Finding Multiple Elements

• To deal with multiple DOM elements


– use the element.all function

element.all(by.css('.selector')).then(function(elements) {
// elements is an array of ElementFinders.
});

• element.all() has several helper functions:


// Number of elements.
element.all(locator).count();

// Get by index (starting at 0).


element.all(locator).get(index);

// First and last.


element.all(locator).first();
element.all(locator).last();
Finding Sub-Elements

• Using a single locator to find:


– an element:

element(by.css('some-css'));

– a list of elements:

element.all(by.css('some-css'));

• Using chained locators to find:


– a sub-element:

element(by.css('some-css')).element(by.tagName('tag-within-css'));

– to find a list of sub-elements:

element(by.css('some-css')).all(by.tagName('tag-within-css'));
Organizing Tests and Refactoring

• Separate E2E tests from unit tests


• Group your E2E tests sensibly
– organize your tests in a way that matches the structure of your project
• If there are multiple pages
– page objects should have a separate directory of their own
• If the page objects have some methods in common
– such as navigateToHome()
– create a base page object
– other page models can inherit from the base page model
• Make your tests independent from each other
– don't want all your tests to fail because of a minor change in the UI
• Keep page object defs free of assertions/expectations
– assertions should be made inside the spec file
Overview of Angular Libraries

• An Angular library
– is an Angular project
– differs from an app in that it cannot run on its own
– must be imported and used in an app

• Libraries extend Angular's base functionality

• For example, to add reactive forms to an app


– add the library package using ng add @angular/forms
– import the ReactiveFormsModule from the @angular/forms library

• Any app developer


– can use these libraries
– other libraries that have been published as npm packages
– by the Angular team or by third parties
Creating Libraries

• For developed functionality that is suitable for reuse


– can create your own libraries
– can be used locally in your workspace
– can publish them as npm packages to share with other projects
• They can be published to:
– the npm registry
– a private npm Enterprise registry
– private package management system that supports npm packages
• Packaging functionality as a library
– forces artifacts to be decoupled from the application's business logic
– can help to avoid various bad practices or architecture mistakes
• Putting code into a separate library
– more complex than simply putting everything in one app
• Requires investment in time and thought
– for managing, maintaining, and updating the library
Creating Libraries

• You can create and publish new libraries


– to extend Angular functionality
– if you find that you need to solve the same problem in more than one
app
– or want to share your solution with other developers), you have a
candidate for a library.

• A simple example might be a button that sends users to


your company website, that would be included in all apps
that your company builds.
The Library Structure

• To create a monorepo workspace


– i.e. one workspace for many apps / libraries

$ ng new dev-workspace --create-application=false

$ cd my-workspace

• Then create libraries


$ ng generate library my-lib

• Or applications
$ ng generate application new-app
The Workspace Structure

• The workspace is built using the following schema:

dev-workspace/
... (workspace-wide config files)
projects/ (generated applications and libraries)
new-app/ --(an explicitly generated application)
... --(application-specific config)
e2e/ ----(corresponding e2e tests)
src/ ----(e2e tests source)
... ----(e2e-specific config)
src/ --(source and support files for application)
my-lib/ --(a generated library)
... --(library-specific config)
src/ --source and support files for library)
The Workspace angular.json File

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"my-lib": {
"projectType": "library",
"root": "projects/my-lib",
"sourceRoot": "projects/my-lib/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/my-lib/tsconfig.lib.json",
"project": "projects/my-lib/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/my-lib/tsconfig.lib.prod.json"
}
...
The Library Structure

• Once created, the library has the following structure:


projects
└── my-lib
├── README.md
├── karma.conf.js
├── ng-package.json
├── package.json
├── src
│ ├── lib
│ │ ├── my-lib.component.spec.ts
│ │ ├── my-lib.component.ts
│ │ ├── my-lib.module.ts
│ │ ├── my-lib.service.spec.ts
│ │ └── my-lib.service.ts
│ ├── public-api.ts
│ └── test.ts
├── tsconfig.lib.json
├── tsconfig.lib.prod.json
├── tsconfig.spec.json
└── tslint.json
Refactoring Parts of an App Into a Library

• To make a solution reusable


– needs to be adjusted
– so that it does not depend on app-specific code
• Declarations such as components and pipes
– should be designed as stateless
– meaning they don’t rely on or alter external variables
• Any observables components subscribe to internally
– should be cleaned up and disposed of during their lifecycle
• Components should expose their interactions
– through inputs for providing context
– outputs for communicating events to other components.
• Services should declare their own providers
– rather than declaring providers in the NgModule or a component
– allows the compiler to leave the service out of the bundle
– if it never gets injected into the application that imports the library
Refactoring Parts of an App Into a Library

• If you register global service providers


– or share providers across multiple NgModules
– use the forRoot() and forChild() patterns provided by RouterModule
• Check all internal dependencies
• For custom classes or interfaces
– used in either components or service
– check whether they depend on additional classes or interfaces
– these may also need to be migrated
• If library code depends on a service
– that service needs to be migrated.
• If library code or its templates depend on other libraries
– such a Angular Material, for instance
– you must configure your library with those dependencies.
Reusable Code and Schematics

• A library is packaged into an npm package


– for publishing and sharing

• This package can also include schematics


– instructions for generating / transforming code directly in your project
– the same way the CLI creates a generic skeleton app
– with ng generate component

• A schematic that is combined with a library can


– provide the Angular CLI with the information it needs
– to generate a component defined in that library
Integrating with the CLI

• A library can include schematics


– that allow it to integrate with the Angular CLI

• Include an installation schematic


– so that ng add can add your library to a project

• Include generation schematics in your library


– so that ng generate can scaffold your defined artifacts in a project
– components, services, tests, and so on

• Include an update schematic


– so that ng update can update your library’s dependencies
– and provide migrations for breaking changes in new releases
Building the Library
$ ng build my-lib
Building Angular Package
********************************************************************
It is not recommended to publish Ivy libraries to NPM repositories.
Read more here: https://v9.angular.io/guide/
ivy#maintaining-library-compatibility
********************************************************************
--------------------------------------------------------------------
Building entry point 'my-lib'
--------------------------------------------------------------------
Compiling TypeScript sources through ngc
Compiling @angular/core : es2015 as esm2015
Bundling to FESM2015
Bundling to FESM5
Bundling to UMD
Minifying UMD bundle
Writing package metadata
Built my-lib
--------------------------------------------------------------------
Built Angular Package
- from: /Users/ewan/dev/dev-workspace/projects/my-lib
- to: /Users/ewan/dev/dev-workspace/dist/my-lib
--------------------------------------------------------------------
Building the Library

$ ng test my-lib
Compiling @angular/animations : es2015 as esm2015
Compiling @angular/common : es2015 as esm2015
Compiling @angular/animations/browser : es2015 as esm2015
Compiling @angular/compiler/testing : es2015 as esm2015
Compiling @angular/core/testing : es2015 as esm2015
Compiling @angular/platform-browser : es2015 as esm2015
Compiling @angular/animations/browser/testing : es2015 as esm2015
Compiling @angular/forms : es2015 as esm2015
...
Compiling @angular/router/testing : es2015 as esm2015
10% building 2/2 modules 0 active06 05 2020 04:57:37.950:WARN [karma]:
No captured browser, open http://localhost:9876/
06 05 2020 04:57:37.952:INFO [karma-server]:
Karma v5.0.4 server started at http://0.0.0.0:9876/
...
TOTAL: 2 SUCCESS
TOTAL: 2 SUCCESS

$ ng lint my-lib
Linting "my-lib"...
All files pass linting.
Publishing a Library

• Use the Angular CLI and the npm package manager


– to build and publish your library as an npm package
– not recommended to publish Ivy libraries to NPM repositories
• Before publishing a library to NPM
– build it using the --prod flag
– will use the older compiler and runtime instead of Ivy.
– known as View Engine

$ ng build my-lib --prod

$ cd dist/my-lib

$ npm publish
Using Your Own Library in Apps

• Don't have to publish libraries to npm package manager


– If they are to be used it in your own apps
– but it does have to be built it first

$ ng build my-lib

• In your apps, import from the library by name:

import { myExport } from 'my-lib';


Angular Service Worker Introduction

• A service worker is a script that runs in the web browse


– manages caching for an application
• Functions as a network proxy
– intercept all outgoing HTTP requests made by the application
– can choose how to respond to them
– completely programmable and doesn't rely on server-specified
caching headers
• Unlike the other scripts that make up an application
– service worker is preserved after the user closes the tab
– next time application is loaded, the service worker loads first
• Even across a fast, reliable network
– round-trip delays can introduce significant latency
– when loading the application
– service workers can significantly improve the user experience
Service Workers in Angular

• Caching an application is like installing a native application


– application is cached as one unit, and all files update together
• Running application continues to run
– with the same version of all files
– does not suddenly start receiving cached files from a newer version
• When users refresh the application
– they see the latest fully cached version
– new tabs load the latest cached code.
• Updates happen in the background
– relatively quickly after changes are published
– previous version of the app served until an update is installed and
ready
• The service worker conserves bandwidth when possible
– resources are only downloaded if they've changed.
Adding a Service Worker to Project

• To set up the Angular service worker in a project


– use the CLI command

$ ng add @angular/pwa --project *project-name*

• The command completes the following actions:


– adds the @angular/service-worker package to your project.
– enables service worker build support in the CLI
– imports and registers the service worker in the app module.
– updates the index.html file
– installs icon files to support the installed Progressive Web App (PWA)
– creates service worker configuration file called ngsw-config.json
Adding a Service Worker to Project - Example

• To set up the Angular service worker in a project


– use the CLI command
$ ng add @angular/pwa --project angular-material

Installing packages for tooling via npm.


Installed packages for tooling via npm.
CREATE ngsw-config.json (620 bytes)
CREATE src/manifest.webmanifest (1356 bytes)
CREATE src/assets/icons/icon-128x128.png (1253 bytes)
CREATE src/assets/icons/icon-144x144.png (1394 bytes)
CREATE src/assets/icons/icon-152x152.png (1427 bytes)
CREATE src/assets/icons/icon-192x192.png (1790 bytes)
CREATE src/assets/icons/icon-384x384.png (3557 bytes)
CREATE src/assets/icons/icon-512x512.png (5008 bytes)
CREATE src/assets/icons/icon-72x72.png (792 bytes)
CREATE src/assets/icons/icon-96x96.png (958 bytes)
UPDATE angular.json (3819 bytes)
UPDATE package.json (1470 bytes)
UPDATE src/app/app.module.ts (1317 bytes)
UPDATE src/index.html (573 bytes)
✔ Packages installed successfully.
Adding a Service Worker to Project - Example

• The command completes the following actions:


– adds the @angular/service-worker package to your project.

$ ls node_modules/@angular/service-worker
README.md esm2015 ngsw-worker.js
bundles esm5 package.json
config fesm2015 safety-worker.js
config.d.ts fesm5 service-worker.d.ts
config.metadata.json ngsw-config.js service-worker.metadata.json
Adding a Service Worker to Project - Example

• The command completes the following actions:


– enables service worker build support in the CLI
angular.json
... other imports
"projects": {
"angular-material": {
"projectType": "application",
...
"configurations": {
"production": {
...
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
...
Adding a Service Worker to Project - Example

• The command completes the following actions:


– imports and registers the service worker in the app module
src/app/app.module.ts
... other imports
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

@NgModule({
// declarations:

imports: [
... other imports
ServiceWorkerModule.register('ngsw-worker.js’,
{ enabled: environment.production }),
],
// providers and bootstrap: [AppComponent],

entryComponents: [
PlayDialogComponent
],
})
export class AppModule { }
Adding a Service Worker to Project - Example

• The command completes the following actions:


– updates the index.html file
src/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular Material</title>
<base href="/">
...
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#1976d2">
</head>
<body>
<app-root></app-root>
<noscript>
Please enable JavaScript to continue using this application.
</noscript>
</body>
</html>
Adding a Service Worker to Project - Example

• The command completes the following actions:


– installs icon files to support the installed Progressive Web App (PWA)
– creates service worker configuration file called ngsw-config.json

CREATE src/assets/icons/icon-128x128.png (1253 bytes)


CREATE src/assets/icons/icon-144x144.png (1394 bytes)
CREATE src/assets/icons/icon-152x152.png (1427 bytes)
CREATE src/assets/icons/icon-192x192.png (1790 bytes)
CREATE src/assets/icons/icon-384x384.png (3557 bytes)
CREATE src/assets/icons/icon-512x512.png (5008 bytes)
CREATE src/assets/icons/icon-72x72.png (792 bytes)
CREATE src/assets/icons/icon-96x96.png (958 bytes)
Adding a Service Worker to Project - Example

• The command completes the following actions:


– creates service worker configuration file called ngsw-config.json
ngsw-config.json
{
"$schema":
"./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}

...
Build the Project

• Finally, the project needs to be built


– the service worker cannot be used via ng serve

$ ng build --prod
Serving With http-server

• To serve the directory containing the web files


– with http-server

$ http-server -p 8080 -c-1 dist/angular-material


Simulating a Network Issue

• To simulate a network issue


– disable network interaction for your application
• In Chrome:
– select More Tools > Developer Tools
– (from the Chrome menu located at the top right corner
– go to the Network tab
– change the Online option to Offline
Simulating a Network Issue

• Now the app has no access to network interaction


– for applications that do not use the Angular service worker
– refreshing now would display Chrome's Internet disconnected page
– saying "There is no Internet connection".
• With the addition of an Angular service worker
– the application behavior changes
– on a refresh, the page loads normally
What is being Cached?

• All files the browser needs to render this app are cached
– the ngsw-config.json boilerplate configuration is set up
– to cache the specific resources used by the CLI
– namely:

• index.html
• favicon.ico
• Build artifacts (JS and CSS bundles)
• Anything under assets
• Images and fonts directly under the configured outputPath
Making Changes to the Application

• Open a new tab in the incognito browser


– this will keep the cache state alive

• Shut down http-server


• Make a change to the application
– watch the service worker install the update
• Edit:
– src/app/welcome/welcome.app.component.html
– change the text The Play Management System
– to The Play Management Platform

• Build and run the server again:


$ ng build --prod

$ http-server -p 8080 -c-1 dist/angular-material


Updating the Application in the Browser

• See how the browser and service worker


– handle the updated application
• Open http://localhost:8080 again in the same window
Updating the Application in the Browser

• Open http://localhost:8080 again in another window


Further Reading

• Many good articles that go into greater depth regarding:


• A Service Worker deep introduction
– https://developers.google.com/web/fundamentals/primers/service-
workers
• The App Shell
– https://angular.io/guide/app-shell
• Service Worker communication
– https://angular.io/guide/service-worker-communications
• Using Service Workers in production
– https://angular.io/guide/service-worker-communications
• Service Worker configuration
– https://angular.io/guide/service-worker-config

You might also like