Angular 2 Workshop

 Gion Kunz

 Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

 Andreas Malär

 Front End Developer

@andreasmalaer

andreas.malaer@oddeven.ch

https://oddeven.ch

Timeline

Time Topic
09:00 Introduction
09:15 Setup
09:30 Modern Web and Angular 2 Theory
10:40 Break 15 min
10:55 Work Units I
12:05 Lunch Break
13:00 Work Units II
14:50 Break 10 min
15:00 Work Units III
16:30 Break 10 min
16:40 TODO Application Walkthrough
17:40 Use Angular 2 Today
18:00 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

- Asynchronous and Reactive Programming

- Change Detection

- NgUpgrade

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!

TypeScipt

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

Start Live Server

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

Chapter 1

Component, template and bootstrap

The @Component annotation

// 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

Bootstrapping

import {bootstrap} from '@angular/platform-browser-dynamic';
import {MyComponent} from './my-component';

bootstrap(MyComponent);

Preparing

Creating a component and bootstrap

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

Exercise

Refactor to use separate file boot.ts for booting and app.ts for our app component

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

Chapter 2.1

Template Syntax: Property and Event Bindings

Property Bindings

@Component({
  selector: 'counter'
  template: '<input type="text" [value]="num">'
})
class App {
  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 App {
  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

Adding a reset button that resets error and input value, use display style block / none to show / hide message

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

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 App {
  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>

Template Elements and the Asterisk (*) Syntax

<ul>
  <li *ngFor="let item of items; let i = index">
    {{item}} at index {{i}}
  </li>
</ul>
<ul>
  <template ngFor #item #i="index" [ngForOf]="items">
    <li>{{item}} at index {{i}}</li>
  </template>
</ul>

is de-sugaring to:

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>',
  pipes: [ReversePipe]
})
export class App {}
@Pipe({
  name: 'reverse'
})
export class ReversePipe {
  transform(value) {
    return value.reverse();
  }
}

App.ts

ReversePipe.ts

Preparing

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

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

Exercise

Create a blacklist pipe that filters rude words from the messages, use the local view variable "even" from NgFor and format paragraph background color on even elements, 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-prepared

Chapter 3

Input and Output Properties

Component Input

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

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

Binding to Inputs

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

Component Output

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

@Component({
  selector: 'explosive',
  template: '<button (click)="onClick()"></button>'
})
export class Explosive{
  @Output() boom = new EventEmitter();

  onClick() {
    this.boom.next('I exploded!');
  }
}

Capturing Output

@Component({
  selector: 'app',
  template: '<explosive (boom)="onBoom($event)"></explosive>',
  directives: [Explosive]
})
export class App {
  onBoom(message) {
    console.log(message);
  }
}

Preparing

Create collapsible component with input and output bindings

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

Exercise

Add a title input for the collapsible component and a closed output property that will emit an event if the collapsible is closed

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

Chapter 4

Content Projection

Content Projection Points

@Component({
  selector: 'container',
  template: '<ng-content></ng-content>'
})
export class Container {}
@Component({
  selector: 'app',
  template: '<container><p>Content</p></container>',
  directives: [Container]
})
export class App {}

Container.ts

App.ts

Selective Projection

@Component({
  selector: 'container',
  template: '<ng-content select="p"></ng-content>'
})
export class Container {}
@Component({
  selector: 'app',
  template: '<container><p>Content</p><container>',
  directives: [Container]
})
export class App {}

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-prepared

Chapter 5

Querying for Children

Querying View Children

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

@Component({
  selector: 'app',
  template: `
    <h1>Hello!</h1>
    <my-component></my-component>
  `,
  directives: [MyComponent]
})
export class App {
  @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 Container {
  @ContentChild(MyComponent) myComp;

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

Preparing

Querying projected content using @ContentChild within the Collapsible component to toggle active state on CollapsibleTitle Component

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

Exercise

Add ViewChild query to App component for querying Collapsible component and toggle it using a button within the App component using the toggle method of the Collapsible component.

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

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

Component Injectors

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

Element Injectors

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

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 pre-existing 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();
  }
}

Preparing

Add @Injectable for CalculationService and use it within the Calculator

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

Exercise

Inject logger service within the App component to log startup, also inject logger service within CalculatorService to log calculation calls, the goal is to have the same logger instance within the App component as in CalculatorService

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

Chapter 7

Asynchronous operations and Reactive Programming

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));

Observe Form Control Changes

import {Control} from '@angular/common';

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

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

Asynchronous Data in the View

@Component({
  selector: 'my-component',
  template: `
    {{data}}
  `
})
export class MyComponent {
  constructor() {
    this.asyncData = Rx.Observable
      .from([1])
      .delay(5000);

    this.asyncSubscription = 
      this.asyncData.subscribe((data) => this.data = data);
  }

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

Using Async Pipe

@Component({
  selector: 'my-component',
  template: `
    {{asyncData | async}}
  `
})
export class MyComponent {
  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

Remove console log from LogService and use Subject to emit events of the logs, store Subject observable in App component and display within view using async pipe

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

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

Detached Change Detection

  • By detaching the component B, D, E and F we're telling Angular that we'd like to decide on our own, when they need to be checked 

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.Detached
})
export class Counter {
  count: number = 0;
  constructor(@Inject(ChangeDetectorRef) cdr) {
    setInterval(() => {
      this.count++;
      cdr.markForCheck();
    }, 1000);
  }
}

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

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-prepared

Walk through
TODO APP

Prepare Todo Project

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

# Change into directory
cd angular-2-todo

# If you're using NVM
nvm use

# Install NPM dependencies (jspm)
npm install

# Install JSPM dependencies
jspm install

Start Live Server

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

Use Angular 2
Today

Should I use Angular 2 for my next project?

It depends...

:-)

Status of Project

  • The project is still in beta
  • No announcement about production release
  • The API is still changing but major bits and pieces are sorted out
  • There are no open bugs that would prevent a large application from running smoothly
  • Documentation is quite OK

NgUpgrade

  • Allows you to run both versions of Angular in parallel
  • Provides Angular 1 <-> Angular 2 interoperability
  • Use Angular 1 directives in Angular 2 component (upgrade)
  • Use Angular 2 components in Angular 1 directives (downgrade)
  • Use Angular 1 providers using @Inject
  • Downgrade Angular 2 Injectables to Angular 1 providers

Migration Path Using NgUpgrade

Preparation

Migration

Angular 1

Angular 2

Preparation

Preparing your application for the change to Angular 2 by following best practices. 

Component structuring, ES6 / TypeScript, only use Services, no $scope.$apply, reactive approach etc.

Migration

Include Angular 2 within application and start migrating piece by piece using NgUpgrade.

Thank You!

 Gion Kunz

 Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

 Andreas Malär

 Front End Developer

@andreasmalaer

andreas.malaer@oddeven.ch

https://oddeven.ch