Angular Workshop

Learn the basics and advanced topics of Angular within our three-day Angular workshop.

by Gion Kunz

Day 1 Topics

  • Tooling (Node, Angular CLI, Webpack)
  • Components, templates and bootstrapping
  • Property and Event Bindings
  • View Variables, Pipes and Template Elements
  • Input and Output properties
  • Container Components and UI Separation

Day 2 Topics

  • Routing
  • Change Detection
  • Performance optimization
  • Testing
  • Data and Reactive Programming with RxJS
  • HttpClient Service

Angular Tooling

Node.js & NPM

  • Used for Tooling (TypeScript, Webpack, SystemJS, AoT Compiler etc.)
  • Can be used for Server-Side-Rendering
  • Angular CLI runs on Node.js
  • Version > 8.9.0 is currently used in Angular

Angular CLI

  • Quickly get started with Angular
  • Whole tooling ecosystem including
  • TypeScript, Karma, Protractor E2E, AoT Compilation and more
  • Generator for project maintenance

Lab 1

Complete local setup and create first project using Angular CLI.

Prerequisites

First Steps

# Install Angular CLI
npm install -g @angular/cli

# Create new project
ng new my-first-app
cd my-first-app

# Create components with generator
ng generate component my-component

# Start dev server
ng serve

# Create production build
ng build

Ngmodule, Components and Bootstrap

The @Component decorator

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

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

NgModules and Components

Module A

Module B

import

1

2

3

4

5

6

7

8

9

Components

Components

runtime

1

2

3

4

5

6

7

8

9

The @NgModule decorator

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

@NgModule({
  imports: [BrowserModule],
  declarations: [Comp1, Comp2, Comp3, ...],
  exports: [Comp1, Comp2, Comp3, ...],
  bootstrap: [Component]
})
export class AppModule {}

Bootstrapping

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

platformBrowserDynamic().bootstrapModule(AppModule);

Component Template Syntax

Expression Bindings

@Component({
  selector: 'app'
  template: '<div>{{message}}</div>'
})
class AppComponent {
  message: string = 'Hello World!';
}
  • Using double curly braces (mustache)
  • Scope is limited to component properties only
  • Supports simple expressions (accessors, operators, function calls etc.)

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>
<p [attr.role]="role"></p>
<p [class.is-active]="isActive()"></p>
<p [style.display]="!isActive() ? 'none' : null"></p>

Regular DOM property

Attribute binding (setAttribute)

CSS class binding (classList)

CSS style binding

Event Bindings

@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)">
  • Use native DOM Event Object
  • No new API to learn
  • Can be used to stop propagation / prevent default browser behavior

Some event bindings support selectors

@Component({
  selector: 'app'
  template: `
    <input (keydown.enter)="submit($event.target.value)">
  `
})
class AppComponent {
  submit(value: string) {
    console.log(value);
  }
}

Repeating template using the NgFor directive

<ul>
  <li *ngFor="let item of items">
    {{item}}
  </li>
</ul>
  • Using *asterisk notation to indicate template elements
  • NgFor DSL (let x of x)

Conditional template using the NgIf directive

<p>Sheeps: {{sheepCount}}</p>
<p *ngIf="sheepCount > 1000">
  You should be asleep now!
</p>
  • Also uses *asterisk syntax, to create template element
  • Will be attached to DOM when condition is truthy, removed when falsey

Lab 3

Create simple click counter component.

Click counter component

  • Create a new counter component with Angular CLI

  • Include a count state within the counter component

  • Create the necessary template to render the current count

  • Provide a button with an event binding to increase the count state in the component on every click by 1

Create a clickable component which will increase a counter when clicked.

Component Communication

Component Input

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

@Component({
  selector: 'app-message',
  template: '{{message}}'
})
export class MessageComponent {
  @Input() message: string;
}
  • Using the @Input decorator on component properties
  • Input can be passed to component by parent component

Binding to Input

@Component({
  selector: 'app-root',
  template: '<app-message [message]="message"></app-message>'
})
export class AppComponent {
  message: string = 'Hello World!';
}

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

Communication in Component Tree

Example: Use component communication to create composition

Lab 4

A small UI Composition Exercise.

Prepare Repository

git clone https://github.com/syncrea/angular-workshop.git
cd angular-workshop
git checkout lab-4
npm install

Refactor ToDo List Application

  • Create a TodoItem component by extracting the relevant portion from TodoList

  • Render the component tree correctly TodoList > TodoItem

  • Pass input correctly and delegate output as required

Refactor an existing ToDo application which consist of only 1 TodoList component into 2 separate components, TodoList and TodoItem.

separation of UI concerns

Mixed Concerns

@Component({
  selector: 'user-profile',
  template: `
    <p class="name">{{name}}</p>
  `
})
class UserProfile {
  constructor() {
    fetch('/api/users/1')
      .then((response) => response.json())
      .then((user) => this.name = user.name);
  }
}
  • UserProfile component does directly connect to data
  • Looses UI composability

Introducing container components

@Component({
  selector: 'user-profile-container',
  template: `
    <user-profile [name]="user.name">
    </user-profile>
  `
})
class UserProfileContainer {
  constructor() {
    fetch('/api/users/1')
      .then((response) => response.json())
      .then((user) => this.user = user);
  }
}
  • Separate Data from UI
  • Render simple UI components and pass data down
  • Manipulate data on output of UI components

Simple UI components

@Component({
  selector: 'user-profile',
  template: `
    <p class="name">{{name}}</p>
  `
})
class UserProfile {
  @Input() name;
}
  • Only display data which gets passed to inputs
  • Delegate actions to parent components using outputs
  • Never manipulate data directly

Listing App using Container Component

  • List container component
  • List UI component
  • List Item UI component
  • UI Actions

Inversion of control with container components

  • Data gets passed down from the container component
  • Actions get delegated upwards in the component tree
  • Data fetching and manipulation only happens in the container components

Container Component

Data

Data

Action

Action

Container Component Wrap Up

  • Helps you to centralize your data fetching / manipulation
    • Transparency and traceability
  • Re-use Components in different context
    • Only the container component decides what data / actions are performed
  • Loose coupling with Input / Output

Lab 5

Let's introduce a container component within our ToDo app.

Prepare Repository

git stash save -u my-lab-4 
git checkout lab-5

Container for ToDo List Application

  • Create a TodoListContainer component

  • Move all data and data manipulation into the newly created container component

  • Render the component tree correctly TodoListContainer > TodoList > TodoItem

  • Pass input correctly and delegate output as required

Let's modify our ToDo list application to include a container component which holds and manipulates our Data.

The Router

A Router serves three main purposes

  • Navigablity & Bookmarkability
  • UI Composition
  • State

Configuration

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {MainComponent} from './main.component.ts';
import {ChildlComponent} from './child1/child1.component.ts';
import {Child2Component} from './child2/child2.component.ts';

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {path: 'childl', component: ChildlComponent},
      {path: 'child2', component: Child2Component}
    ])
  ],
  declarations: [MainComponent, ChildlComponent, Child2Component],
  bootstrap: [MainComponent]
})
export class MainModule {}

Redirects and Fallback Component

RouterModule.forRoot([{
  path: 'child1',
  component: Child1Component
},{
  path: '',
  pathMatch: 'full',
  redirectTo: '/child1'
},{
  path: '**',
  component: PageNotFoundComponent
}];
  • Supports default (empty) path match
  • Supports redirects
  • Supports fallback routes
  • Sequence matters, first match wins strategy

Router Directives

Router Outlet

<h1>Website</h1>

<main class="content">
  <router-outlet></router-outlet>
</main>
  • Marks the position where Angular will put routed components
  • Can be nested
  • Can be named for auxiliary routes

Router Link Directives

<h1>Welcome!</h1>
<nav>
  <a routerLink="/child1"
     routerLinkActive="active">Child1</a>
  <a routerLink="/child2"
     routerLinkActive="active">Child2</a>
</nav>
<router-outlet></router-outlet>
  • Generate links to routed components
  • Use RouterLinkActive to attach CSS classes on active routes
  • Supports Router DSL

Route Parameters

Route Configuration with Parameters

RouterModule.forRoot([{
  path: 'child/:id',
  component: ChildComponent
}])
  • Use placeholders in route configurations
  • Parameters in the URL will be parsed and passed to the activated components

Accessing Parameters in Routed Components using Snapshot

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

@Component({
  selector: 'ngc-child',
  template: 'Child:<p>Param: {{params.id}}</p>'
})
export class ChildComponent {
  constructor(route: ActivatedRoute) {
    this.params = route.snapshot.params;
  }
}
  • Access route parameters by injecting ActivatedRoute
  • Use snapshot to get current parameter value

By default, the router re-uses a component instance when it navigates to the same component.

Using Observable Parameters

@Component({
  selector: 'ngc-child',
  template: `
   Child:
    <p>Param: {{params.id}}</p>`
})
export class ChildComponent {
  params: any;

  constructor(route: ActivatedRoute) {
    route.params.subscribe((params) => {
      this.params = params;
    });
  }
}
  • Params is an RxJS Observable
  • We can subscribe to parameter changes using a callback

Dream team: Router & Container components

@Component({
  selector: 'app-details-container',
  template: '<app-details [item]="item"></app-details>'
})
export class DetailsContainerComponent {
  item: Item;

  constructor(route: ActivatedRoute, itemService: ItemService) {
    route.params.subscribe(
      (params) => this.item = itemService.getItemById(params.id)
    );
  }
}

Lab 6

Using the router to create a details view of ToDo items.

Prepare Repository

git stash save -u my-lab-5 
git checkout lab-6

Detail view for ToDo List Application

  • Replace the static reference to <app-todo-list-container> in the app.component.html template with a <router-outlet> element to enable the router

  • Include a second route configuration in your main module which includes a parameter :no and routes to the TodoDetailsContainer component

  • Implement the showDetails method on the TodoListContainer component and navigate to the newly created details route

We want to use the Angular Router to create a navigable detail view in our ToDo application.

Change Detection and Performance

By default Angular Checks every binding of every component on any event

Default Change Detection

Click Event

Pure Components

Pure component only change, if their input is changing.

Is this a pure Component?

@Component({
  selector: 'ngc-list-item',
  template: `
    <div class="content">{{item}}</div>
    <button (click)="remove()" class="remove">Remove</button>`
})
export class ListItemComponent {
  @Input() item: string;
  @Output() onRemove = new EventEmitter<never>();
  
  remove() {
    this.onRemove.emit();
  }
}

"OnPush" Change Detection Strategy

@Component({
  selector: 'ngc-list-item',
  template: `
    <div class="content">{{item}}</div>
    <button (click)="remove()" class="remove">Remove</button>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListItemComponent {
  @Input() item: string;
  @Output() onRemove = new EventEmitter<never>();
  
  remove() {
    this.onRemove.emit();
  }
}

"OnPush" Change Detection

Click Event

Build Pure Components!

  • Performance Benefit
  • Simple to reason about
  • Easy to Reuse (no side effects)

Immutable Data

How to treat your data

const person = {name: 'Peter'};
person.name = 'Peach';


const numbers = [1, 2, 3, 4];
numbers[1] = 20;
  • You don’t need to use a fancy library like immutable.js
  • Embrace immutability as a concept
person = {name: 'Peach'};
numbers = numbers.slice();
numbers[1] = 20;

Prefer ES5.1 Array extras

let numbers = [1, 2, 3, 4];
// Mapping to new array
numbers = numbers.map(number => number * 10);
// Filtering an array
numbers = numbers.filter(number => number % 2 === 0);

Spread Operator

let numbers = [1, 2, 3, 4];

// Appending
numbers = [...numbers, 5];

// Prepending 
numbers = [0, ...numbers];

// Convert mutable op to immutable
numbers = [...numbers].sort();
  • The spread operator helps you to perform immutable operations
  • Quickly clone arrays

Object Property Spread (ES2018)

// Replace one or many properties
let person = {name: 'Peter', age: 30};
person = {...person, name: 'Peach'};

// Deep immutable operations
let course = {
  title: 'Angular Workshop',
  person: {name: 'Peter', age: 30}
};
course = {
  ...course,
  person: {...course.person, name: 'Peach'}
};
  • Transpiles to Object.assign
  • Very useful when treating your data immutably

Embrace Immutablity!

  • Avoid nasty side effects
  • Benefit from “OnPush” change detection in pure components
  • Usually Performance is not a problem
  • Use Immutable.js in high-performance cases

Lab 7

Work with Pure Components and optimize change detection.

Prepare Repository

git stash save -u my-lab-6 
git checkout lab-7

Pure Components for our ToDo List

  • Change all components to use ChangeDetectionStrategy.OnPush

  • Switch all data manipulation within the TodoService to use immutable operations

We want to switch our ToDo List application to use Pure component using “OnPush” change detection and immutable data.

TESTING

Testing a simple Counter Component

Testing with TestBed

import {
  TestBed, 
  ComponentFixture} from '@angular/core/testing';
import {MainComponent} from './main.component';
import {CounterService} from './counter.service';

describe('MainComponent', () => {
  let fixture: ComponentFixture<MainComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [MainComponent],
      providers: [CounterService]
    });

    fixture = TestBed.createComponent(MainComponent);
  });
});
  • Use TestBed to create dynamic test module

  • Include Components and dependencies within the test module

Component Fixture

class ComponentFixture<T> {
    debugElement: DebugElement
    componentInstance: T
    nativeElement: any
    elementRef: ElementRef
    changeDetectorRef: ChangeDetectorRef
    componentRef: ComponentRef<T>
    detectChanges(): void
    isStable(): boolean
    whenStable(): Promise<any>
    destroy(): void
}
  • Wrapper around instantiated component

  • Access debug element, native host element and change detector

Debug Element

class DebugElement {
    nativeElement: any
    query(predicate: Predicate<DebugElement>): DebugElement
    queryAll(predicate: Predicate<DebugElement>): DebugElement[]
    children: DebugElement[]
    triggerEventHandler(eventName: string, eventObj: any)
}
  • Wrapper around component host element

  • Allows to travers through DebugElement tree (DOM like)

  • Can be used to tigger events

Query for DebugElements

import {DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';

const countElement = fixture.debugElement
  .query(By.css('.count'))
  .nativeElement;

console.log(countElement.textContent);

Triggering Events on DebugElements

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

const buttonDebugElement: DebugElement = 
  fixture.debugElement.query(By.css('button'));

buttonDebugElement.triggerEventHandler('click', null);

Mocking dependencies

import {CounterService} from './counter.service';

export class CounterServiceMock() {
  increment() {}
}

TestBed.configureTestingModule({
  declarations: [MainComponent],
  providers: [{
    provide: CounterService, 
    useClass: CounterServiceMock
  }]
});

Lab 8

Testing our ToDo App.

Prepare Repository

git stash save -u my-lab-7 
git checkout lab-8

Testing our ToDo components

  • Create a TestBed setup where you include the TodoListContainer, TodoList and TodoItem components into the declarations

  • Provide your own mock TodoService which will be used by the TodoListContainer

  • Use the ComponentFixture debug element to query for a TodoItem and tigger a click event on the checkbox element

  • Assert on the state within your mock TodoService

We want to include a test for our Todo Application where we verify that when we tick off a Todo Item, it will be updated within our state.

Asynchronous Data & Reactive Programming

Reactive Programming with RxJS

Observable Streams

Item

Error

Completed

Simple Example

import {from} from 'rxjs';
import {filter, map} from 'rxjs/operators';

from([1, 2, 3])
  .pipe(
    map(num => num * num),
    filter(num => num > 3)
  )
  .subscribe(item => console.log(item));
  • Items are emitted through a stream of items

  • Each item flows through the stream individually

  • The subscription will cause the stream start flowing

Chaining Streams with switchMap

import {from, timer} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';

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

RxJS and Angular

RxJS is an integral part of Angular

  • Route Parameters

  • HttpClient Service

  • Reactive Forms

  • Component Output (EventEmitter)

Using Observables in Components

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

  constructor() {
    this.asyncSubscription = of(1)
      .pipe(delay(2000))
      .subscribe(data => this.data = data);
  }

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

  • Subscriptions should be cleaned up using unsubscribe()

  • Within components, the ngOnDestroy lifecycle hook is a good spot for this task

  • Feels cumbersome and inconvenient

Subscribe in the component view

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

  constructor() {
    this.asyncData = of(1)
      .pipe(delay(2000));
  }
}

  • The async Pipe is creating the subscription in the view

  • Always the latest item in the stream will be rendered

  • When the component view is destroyed, the subscriptions is automatically unsubscribed

Angular HttpClient Service

HttpClient returns Observables

@Component({
  selector: 'my-component',
  template: `
   <ul>
    <li *ngFor="let item of items | asnyc">{{item}}</li>
   </ul>`
})
export class MyComponent {
  items: Observable<Item[]>;

  constructor(http: HttpClient) {
    this.items = this.http
      .get<Item[]>('/api/items');
  }
}

HttpClient API

class HttpClient {
  delete<T>(url: string, options?: {...}): Observable<T>
  get<T>(url: string, options?: {...}): Observable<T>
  head<T>(url: string, options?: {...}): Observable<T>
  jsonp<T>(url: string, callbackParam?: string): Observable<T>
  options<T>(url: string, options?: {...}): Observable<any>
  patch<T>(url: string, body: any | null, options?: {...}): Observable<T>
  post<T>(url: string, body: any | null, options?: {...}): Observable<T>
  put<T>(url: string, body: any | null, options?: {...}): Observable<T>
}

Lab 9

Use HttpClient service to load data asynchronously.

Prepare Repository

git stash save -u my-lab-8 
git checkout lab-9

Using a fake web service

  • Inject HttpClient into your TodoService

  • Create the necessary calls within TodoService to the backend

    • GET /api/todos

    • PUT /api/todos

    • GET /api/todos/:id

    • POST /api/todos/:id

Let’s connect our Todo Application to a fake RESTful backend using the HttpClient service.

Workshop Recap

Day 1

  • Tooling (Node, Angular CLI, Webpack)
  • Components, templates and bootstrapping
  • Property and Event Bindings
  • View Variables, Pipes and Template Elements
  • Input and Output properties
  • Container Components and UI Separation

Day 2

  • Routing
  • Change Detection
  • Performance optimization
  • Testing
  • Data and Reactive Programming with RxJS
  • HttpClient Service

Let's create together.

Thanks!

Angular Workshop

By Gion Kunz

Angular Workshop

Slides for the syncrea Angular Workshop

  • 792