Angular Workshop

 Gion Kunz

 Front End Developer

gion.kunz@oddeven.ch

https://oddeven.ch

Timeline

Time Topic
08:30 Introduction
08:45 Setup
09:00 Modern Web and Angular 2 Theory
10:10 Break 15 min
10:25 Work Units I
12:00 Lunch Break
13:00 Work Units II
14:50 Break 10 min
15:00 Work Units III
16:30 Break 10 min
17:30 Finish

What we do

- ES6, ES7 and TypeScript

- Web Components Basics

- SystemJS and JSPM

- Components, templates and bootstrapping

- Property and Event Bindings

Morning

- View Variables, Pipes and Template Elements

- Input and Output properties

- Content Projection

- Querying Child Directives

- Dependency Injection

- Reactive Programming with RxJS

- Change Detection

Afternoon

What we don't
Do

- Directives

- Router

- Testing

Setup

Using the Workshop VM

You're all set to go :-)

Using your own operating system

The ModERN WEB

The future is

now

ES6

class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    console.log(this.name + ': Hi there!');
  }
  
  static createPerson(name) {
    return new Person(name);
  }
}

var gion = new Person('Gion');
gion.sayHello();

ES6 Class Syntax

ES6 Class De-sugared

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function sayHello() {
  console.log(this.name + ': Hi there!');
};

Person.createPerson = function createPerson(name) {
  return new Person(name);
};

var gion = new Person('Gion');
gion.sayHello();

ES6 Class Inheritance

class Organism {}

class Person extends Organism {
  constructor(name) {
    super();
    this.name = name;
  }
}

class Developer extends Person {
  constructor(name, lovesAngular) {
    super(name);
    this.lovesAngular = lovesAngular;
  }
}

var gion = new Developer('Gion', true);
console.log(gion);

ES6 Module Syntax

export var someVariable = 10;
export function someFunction() {};
export default class SomeClass {};

Module A

Module B

// Multiple imports
import {someVariable, someFunction} from 'A';
// Import alias
import {someVariable as myLocalVariable} from 'A';
// Wildcard import
import * as A from 'A';
// Import the default export
import SomeClass from 'A';

ES6 Template Strings

var num = 100;
var str = `The square root of ${num} is ${Math.sqrt(num)}`;
console.log(str);

var className = 'my-class';
var title = 'Welcome';
var html = `
  <div class="${className}">
    <h1>${title}</h1>
  </div>
`;
console.log(html);

Without Template Strings

var num = 100;
var str = 'The square root of ' + num + ' is ' + Math.sqrt(num);
console.log(str);

var className = 'my-class';
var title = 'Welcome';
var html = '\n' +
  '<div class="' + className + '">\n' +
  '  <h1>' + title + '</h1>\n' +
  '</div>\n';
console.log(html);

ES6 Arrow Functions

var square = x => x * x;
var multiply = (a, b) => a * b;

console.log(square(10));
console.log(multiply(3, 7));

Arrow Functions Bind "this" from Parent Scope

var Counter = {
  count: 0,
  start() {
    setInterval(() => this.count++, 1000)
  }
};

Without Arrow Functions

var Counter = {
  count: 0,
  start: function() {
    setInterval(function() {
      this.count++;
    }.bind(this), 1000);
  }
};

Borrowed From

The Future

ES7

ES7 Proposal "Class fields and static properties"

class Person {
  static sex = {
    female: 1, 
    male: 2,
    unknown: 3
  };
  name = 'no-name';
  sex = Person.sex.unknown;
}

ES7 Proposal "Decorators"

function LogAccess(obj, prop, descriptor) {
  const delegate = descriptor.value;
  descriptor.value = function() {
    console.log(`${prop} was called!`);
    return delegate.apply(this, arguments);
  };
}

class MoneySafe {
  @LogAccess
  openSafe() {
    this.open = true;
  }
}

const safe = new MoneySafe();
safe.openSafe(); // openSafe was called!

TypeScRipt

Static Typing

function add(a: number, b: number): number {
  return a + b;
}

console.log(add(10, 4));

Typing with Generics

class ListWrapper<T> {
  list: Array<T> = [];
  
  add(item: T) {
    this.list.push(item);
  }

  get(index: number): T {
    return this.list[index];
  }
}

var listWrapper: ListWrapper<string> = new ListWrapper();
listWrapper.add('Hello');
console.log(listWrapper.get(0));

Web Components

The Standard

Custom Elements

HTML Imports

Templates

Shadow DOM

Templates

<body>
<template id="template">
  <h1>This is a template!</h1>
</template>
</body>
var template = document.querySelector('#template');
var instance = document.importNode(template.content, true);
document.body.appendChild(instance);

Shadow DOM

<body>
<div id="host"></div>
</body>
var host = document.querySelector('#host');
var root = host.createShadowRoot();
var elem = document.createElement('h1');
elem.textContent = 'Bombaclat!';
root.appendChild(elem);

Angular 2 Architecture

Angular 1

Angular 2

Component Lifecycle

Module Loader

SystemJS with JSPM

The bread and butter for modern front-ends

  • SystemJS - is a module loader that uses the module-loader-polyfill (ES6 loader specification)
  • JSPM - is a universal package manager that installs from anywhere (bower, npm, github etc.) and creates SystemJS configurations

index.html

<head>
  <title>My First Project</title>
  <script src="jspm_packages/system.js"></script>
</head>
<body>
  <script>
    System.config({
      transpiler: "typescript"
    })
    System.import('./app.ts');
  </script>
</body>

app.ts

export class HelloWorld {
  static sayHello() {
    console.log('Hello World!');
  }
}

HelloWorld.sayHello();

Let's Hack

Prepare Work Units Project

# Clone project with git
git clone https://github.com/oddeven/angular-2-workshop.git

# Change into directory
cd angular-2-workshop

# NPM Install for Angular typings
npm install

Start Live Server

# This will start a simple server that
# will reload the browser if files change
live-server

Chapter 1

NgModule, Component, template and bootstrap

The @Component decorator

// The component annotation can be found 
// in the core module
import {Component} from '@angular/core';

@Component({
  // A CSS like selector that will tell
  // Angular where to attach our component
  selector: 'my-component',
  // The HTML template that is going to be
  // used as view
  template: '<p>Hello World!</p>'
})
export class MyComponent {}

The Component Host Element

@Component({
  selector: 'my-component'
  template: '<p>Hello World!</p>'
})
<body>
  <my-component></my-component>
</body>

+

=

<body>
  <my-component>
    <p>Hello World!</p>
  </my-component>
</body>
<my-component>
</my-component>

Host Element

NgModules and Components

Module A

Module B

Module C

1

2

3

4

5

6

7

8

The @NgModule decorator

// The ng module annotation can be found 
// in the core module
import {NgModule} from '@angular/core';
// Other core and supplementing parts of 
// angular are also organized in modules
import {BrowserModule} from '@angular/platform-browser';

@NgModule({
  // What modules should be imported in the context 
  // of this module
  imports: [BrowserModule],
  // Declare any components to be used within this module
  declarations: [Comp1, Comp2, Comp3, ...],
  // Declare what components should be made available to
  // other modules
  exports: [Comp1, Comp2, Comp3, ...],
  // Specify which components should be bootstrapped
  bootstrap: [Component]
})
export class AppModule {}

Bootstrapping

import {platformBrowserDynamic} 
  from '@angular/platform-browser-dynamic';
import {AppModule} from './app-module';

platformBrowserDynamic().bootstrapModule(AppModule);

Preparing

Creating a component and bootstrap

# We are on a branch
git checkout work-unit-1

Exercise

Refactor to use separate files

  1. app-component.ts for our component

  2. app-module.ts for our NgModule

  3. boot.ts for bootstrapping

# We are on a branch
git checkout work-unit-1

Chapter 2.1

Template Syntax: Property and Event Bindings

Property Bindings

@Component({
  selector: 'counter'
  template: '<input type="text" [value]="num">'
})
class AppComponent {
  num: number = 0;

  constructor() {
    setInterval(() => this.num++, 1000);
  }
}

Different Property Binding Types

<p [title]="title"></p>

Regular DOM property

<p [attr.role]="role"></p>

Attribute binding (setAttribute)

<p [class.is-active]="isActive()"></p>

CSS class binding (classList)

<p [style.display]="!isActive() ? 'none' : null"></p>

CSS style binding

Event Binding

@Component({
  selector: 'app'
  template: `
    <button (click)="onClick()">Click me</button>
  `
})
class AppComponent {
  onClick() {
    alert('Cool!');
  }
}

Local View Variable $event

<input (input)="handleInput($event.target.value)">

Preparing

Creating element property bindings, class bindings, style bindings, attribute bindings, add event binding

# We are on a branch
git checkout work-unit-2-1

Exercise

  1. Adding a reset button that resets error and input value

  2. Use display style block / none to show / hide message

# We are on a branch
git checkout work-unit-2-1

Chapter 2.2

Template Syntax: Local View Variables, Pipes and
Template Elements

Using Local View References

@Component({
  selector: 'app'
  template: `
    <input #inp type="text">
    <button (click)="onClick(inp.value)">Click Me</button>
  `
})
class AppComponent {
  onClick(value) {
    alert(`The value is ${value}`);
  }
}

NgFor directive uses local view variables

<ul>
  <li *ngFor="let item of items">
    {{item}}
  </li>
</ul>

Expose more Local View Variables from NgFor

<ul>
  <li *ngFor="let item of items; let i = index">
    {{item}} at index {{i}}
  </li>
</ul>

More core Directives that Support Template Syntax

<p *ngIf="isActive()"></p>
<div [ngSwitch]="value">
  <p *ngSwitchWhen="1">One</p>
  <p *ngSwitchWhen="2">Two</p>
  <p *ngSwitchDefault>Other</p>
</div>

NgIf

NgSwitch

Pipes

@Component({
  selector: 'app',
  template: '<p>{{data | reverse}}</p>'
})
export class App {}
@Pipe({
  name: 'reverse'
})
export class ReversePipe {
  transform(value) {
    return value.reverse();
  }
}

app-component.ts

reverse-pipe.ts

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app-component';
import {ReversePipe} from './reverse-pipe';

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

}

app-module.ts

Preparing

Using NgFor to list messages, local view variable to bind array elements and index, add input element with local view reference, addMessage method and button to trigger, pipe ToUpperPipe

# We are on a branch
git checkout work-unit-2-2

Exercise

  1. Create a character count pipe that apends a character count to each message

  2. Use the local view variable "even" from NgFor and format paragraph background color on even elements

  3. Pass HTMLInputElement to addMessage instead of value and reset value field after message is added

# We are on a branch
git checkout work-unit-2-2

Chapter 3

Input and Output Properties

Component Input

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

@Component({
  selector: 'person',
  template: '{{details.name}}'
})
export class PersonComponent {
  @Input() details;
}

Binding to Inputs

@Component({
  selector: 'app',
  template: '<person [details]="person"></person>'
})
export class AppComponent {
  person = {
    name: 'Gion'
  };
}

Component Output

import {Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'timer',
  template: '<button (click)="startTimer()">Start</button>'
})
export class TimerComponent {
  @Output() timeout = new EventEmitter();

  startTimer() {
    setTimeout(
      () => this.timeout.emit('I timed out!'),
      5000
    );
  }
}

Capturing Output

@Component({
  selector: 'app',
  template: '<timer (timeout)="onTimeout($event)"></timer>'
})
export class AppComponent {
  onTimeout(message) {
    console.log(message);
  }
}

Preparing

Collapsible component with input and output bindings

# We are on a branch
git checkout work-unit-3

Exercise

  1. Add a title input for the collapsible component

  2. Add a closed output property that will emit an event if the collapsible is closed

# We are on a branch
git checkout work-unit-3

Chapter 4

Content Projection

Content Projection Points

@Component({
  selector: 'container',
  template: '<ng-content></ng-content>'
})
export class ContainerComponent {}
@Component({
  selector: 'app',
  template: '<container><p>Content</p></container>'
})
export class AppComponent {}

Container.ts

App.ts

Selective Projection

@Component({
  selector: 'container',
  template: '<ng-content select="p"></ng-content>'
})
export class ContainerComponent {}
@Component({
  selector: 'app',
  template: '<container><p>Content</p><container>'
})
export class AppComponent {}

Container.ts

App.ts

Preparing

Make content of collapsible projectable

# We are on a branch
git checkout work-unit-4

Exercise

Add selective content projection using the select attribute for adding a title to our collapsible

# We are on a branch
git checkout work-unit-4

Chapter 5

Querying for Children

Querying View Children

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

@Component({
  selector: 'app',
  template: `
    <h1>Hello!</h1>
    <my-component></my-component>
  `
})
export class AppComponent {
  @ViewChild(MyComponent) myComp;

  ngAfterViewInit() {
    this.myComp.doSomething();
  }
}

Some Variations

@ViewChildren(MyComponent) myComp: QueryList<MyComponent>;

Query multiple children using a live list of view children

@ViewChild('myComp') myComp: MyComponent;

Query for a component using its view reference

<my-component #myComp></my-component>
@ViewChild('myInput') myInput: ElementRef;

Query for a DOM element using its view reference

<input type="text" #myInput>

Querying Content Children

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

@Component({
  selector: 'container',
  template: '<ng-content></ng-content>'
})
export class ContainerComponent {
  @ContentChild(MyComponent) myComp;

  ngAfterContentInit() {
    this.myComp.doSomething();
  }
}
@Component({
  selector: 'app',
  template: '<container><my-component></my-component></container>'
})
export class AppComponent {}

Preparing

A blinking timer signal projected into a control field using content projection.

# We are on a branch
git checkout work-unit-5

Exercise

  1. Add more blinking timer signals to the field

  2. Use ContentChildren instead of ContentChild to control all projected signals

# We are on a branch
git checkout work-unit-5

Chapter 6

Dependency Injection

Injection Resolution Stages

  • In Angular 2 the Injector is inherited through the component and DOM tree
  • Providers can be overriden at any stage
  • Instances of providers in DI are not necessarily singletons anymore

Pre Existing Injectors

  • The terminal injector throws exception or resolves @Optional to null
  • The platform injector resolves browser singleton resources like cookies and location

Pre Existing Injectors

App Injectors

  • Each bootstrapped module will be based on a root app injector
  • There can be multiple apps on one page
  • All app injectors have the platform injector as parent

Pre Existing Injectors

App Injector

Component Injectors

  • Each component has its own injector
  • Inherited through DOM order of nested components

Pre Existing Injectors

App Injector

Component Injectors

Element Injectors

  • Each element within the shadow DOM of a component has its own injector
  • Inherited through DOM order of nested elements

Pre Existing Injectors

App Injector

Component Injectors

Element
Injectors

Injection Resolution Order

  1. Dependencies on the current element
  2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
  3. Dependencies on component injectors and their parents until it encounters the root component
  4. Dependencies on App Injector
  5. Dependencies on pre-existing injectors

Pre Existing Injectors

App Injector

Component Injectors

Element
Injectors

Creating Injectable Classes

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

@Injectable()
export class MyService {}

Specifying Injectables at Component Level and Inject in Constructor

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

@Component({
  selector: 'my-component',
  template: '<p>{{data}}</p>',
  providers: [MyService]
})
export class MyComponent {
  constructor(@Inject(MyService) myService) {
    this.data = myService.getData();
  }
}

Specifying Injectables at App Injector Level

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

@NgModule({
  ...
  providers: [MyService]
})
export class MyModule {

}

Preparing

Add @Injectable for CalculationService and use it within the Calculator

# We are on a branch
git checkout work-unit-6

Exercise

  1. Inject logger service within the App component to log startup

  2. Also inject logger service within CalculatorService to log calculation calls

  3. The goal is to have the same logger instance in the App component and in the CalculatorService

# We are on a branch
git checkout work-unit-6

Chapter 7

Asynchronous operations and Reactive Programming

What is an Observable?

Stream of Items

Item

Error

Completed

Reactive Programming with Rx.js

Observables

import {Observable} from 'rxjs/Rx';

Observable
  .from([1, 2, 3])
  .map((num) => num * num)
  .subscribe((item) => console.log(item));

Build Reactive Forms

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ReactiveFormsModule} from '@angular/forms';

@NgModule({
  imports: [BrowserModule, ReactiveFormsModule],
  ...
})
export class AppModule {}

Observe Form Control Changes

import {FormControl} from '@angular/forms';

@Component({
  selector: 'my-component',
  template: `
    <input [formControl]="num" type="number">
    <output>{{out}}</output>
  `
})
export class MyComponent {
  out: number;
  num: FormControl = new FormControl();

  constructor() {
    this.num.valueChanges
      .subscribe((value) => this.out = value);
  }
}

Asynchronous Data in the View

@Component({
  selector: 'my-component',
  template: `
    {{data}}
  `
})
export class MyComponent {
  asyncSubscription: Subscription;
  data: number;

  constructor() {
    this.asyncSubscription = Observable
      .from([1])
      .delay(5000)
      .subscribe((data) => this.data = data);
  }

  ngOnDestroy() {
    this.asyncSubscription.unsubscribe();
  }
}

Using Async Pipe

@Component({
  selector: 'my-component',
  template: `
    {{asyncData | async}}
  `
})
export class MyComponent {
  asyncData: Observable<number>;

  constructor() {
    this.asyncData = Rx.Observable
      .from([1])
      .delay(5000);
  }
}

Preparing

Switch to an asynchronous CalculatorService and use Rx.js to build reactive components

# We are on a branch
git checkout work-unit-7

Exercise

  1. Remove console log from LogService

  2. Use Subject in LogService to emit logs

  3. Display observable logs within AppComponent view using async pipe

# We are on a branch
git checkout work-unit-7

Chapter 8

Change Detection

Default Change Detection

  • By default Angular will check all component for changes (dirty checking) on every single browser event (zone.js)
  • This can be a costly operation with many components

Pure Components

  • By using the strategy OnPush we can tell the change detector to only check if the input parameters of a component change
  • If we're using immutable data this is the simplest and most effective form of change detection

Immutable Data

var array = [1, 2, 3];

// We update an element but the
// reference does not change
array[0] = 2;
var array = [1, 2, 3];

// We use slice to create a copy
// of the array first
array = array.slice();
array[0] = 2;

Adding item by mutating array

Adding item immutable

Mark for Check

  • We can manually flag the component E (and its path to the root component) to be checked  using the change detector on the component

Handling Change Detection

import {
  ChangeDetectionStrategy, 
  ChangeDetectorRef} from '@angular/core';

@Component({
  selector: 'counter',
  template: '{{count}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Counter {
  count: number = 0;
  constructor(@Inject(ChangeDetectorRef) cdr) {
    setInterval(() => {
      this.count++;
      cdr.markForCheck();
    }, 1000);
  }
}

Preparing

Use ChangeDetectionStrategy.OnPush with mutable data, show immutable behaviour of ListItem component, add naive change detection in List component

# We are on a branch
git checkout work-unit-8

Exercise

Switch to immutable data by always making a copy of the name list on modification and take advantage of change detection initiated by reference changes when using the OnPush strategy

# We are on a branch
git checkout work-unit-8

Thank You!

 Gion Kunz

 Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

Angular 2 Workshop

By oddEVEN

Angular 2 Workshop

  • 2,583