Angular
One Framework

 Gion Kunz

 Front End Developer

gion.kunz@oddeven.ch

https://oddeven.ch

  • Smart-alecky hardcore programmer

  • Full-hearted HX advocate

  • Enthusiastic teacher

  • Full-stack front-end developer

  • Main profession: Fisherman

&

  • Added "first" alias when using ngFor

  • Added support to render SVG foreignObjcet

  • I18N added the MissingTranslationStrategy

2nd Edition coming in Aug / Sep 2017

Topics

  • Getting Started

  • Why Components?

  • Angular by Example

  • Conclusion & Summary

  • Questions

Getting Started

Angular
Architecture

Angular 1

Angular: One Framework

Where to Start?

  • Angular CLI

  • Angular Seed (by @mgechev)

  • Angular 2 Webpack Starter

  • Roll Your Own with JSPM & SystemJS

Why Components?

The parable of the two watchmakers

Herbert A. Simon, The Sciences of the Artificial (1969)

Hora

Tempus

Once upon a time, there were two watchmakers, named Hora and Tempus, who manufactured very fine watches...

Hora's Shop

Tempus' Shop

They both received a lot of orders and made lots of money...

Hora's Shop

Tempus' Shop

However, Hora prospered while Tempus went broke and lost his shop...

Why did Hora SUCCEED?

Watch manufacturing process

Tempus' Manufacture

  • Consists of ~1000 parts

  • Assembled of individual parts in one go

  • Fell apart completely, if he needed to answer calls

Hora's Manufacture

  • Consists of ~1000 parts

  • Assembled of intermediate stable components

  • Fell apart into components only

...complex systems evolve from simple systems much more rapidly when there are stable intermediate forms...

 

Herbert A. Simon

and so on...

commonalities

Artificial

Nature

Microcosm

Macrocosm

Fundamental

Significant

We Tend to Develop on Two Levels only

Pages

UI Elements

We need to build user interfaces like this...

...not like that

Don't create components for reusability, create components for simplicity!

<div class="task-list__l-container">
  <div class="task-list__l-box-c">
    <div class="task-list__tasks">
      <div *ngFor="let task of tasks" class="task">
        <div class="task__l-box-a">
          <div class="checkbox">
            <label class="checkbox__label">
              <input class="checkbox__input" type="checkbox"
                     [checked]="task.done"
                     (change)="task.done = $event.target.checked">
              <span class="checkbox__text"></span>
            </label>
          </div>
        </div>
        <div class="task__l-box-b">
          <div class="task__title">{{task.title}}</div>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="task-list__l-container">
  <div class="task-list__l-box-c">
    <div class="task-list__tasks">
      <ngc-task *ngFor="let task of tasks" 
                [task]="task"
                class="task"></ngc-task>
    </div>
  </div>
</div>

Task List

<div class="task__l-box-a">
  <ngc-checkbox [(checked)]="task.done"></ngc-checkbox>
</div>
<div class="task__l-box-b">
  <div class="task__title">{{task.title}}</div>
</div>

Task

<label class="checkbox__label">
  <input class="checkbox__input" type="checkbox"
         [checked]="checked"
         (change)="onCheckedChange($event.target.checked)">
  <span class="checkbox__text">{{label}}</span>
</label>

Checkbox

Keep it simple!

  • Allows to Focus

  • Fitness / Ready for Change

  • Flexibility

  • Reduce atomistic Complexity

Angular by Example

Components

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

modules

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

View

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

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

Composition

Intrinsic vs. Extrinsic Composition

Intrinsic

Extrinsic

What do you put into the empty jar?

What do you put into the empty jar?

Content Projection

@Component({
  selector: 'jar',
  template: '<ng-content></ng-content>'
})
export class Jar {}
@Component({
  selector: 'app',
  template: '<jar><p>Content</p></jar>'
})
export class App {}

Jar

App

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

@Component({
  selector: 'ngc-app',
  template: `
    <ngc-collapsible title="Click to reveal content">
      <p>I'm the content of the collapsible</p>
    </ngc-collapsible>
  `
})
export class App {}

App

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

@Component({
  selector: 'ngc-collapsible',
  template: `
    <div (click)="toggle()">
      {{title}}
    </div>
    <div *ngIf="active"
         class="content">
      <ng-content></ng-content>
    </div>
  `
})
export class Collapsible {
  @Input() title: string;
  active: boolean;
  toggle() {
    this.active = !this.active;
  }
}

Collapsible

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

@Component({
  selector: 'ngc-app',
  template: `
    <ngc-tabs>
      <ngc-tab name="Tab One">Content 1</ngc-tab>
      <ngc-tab name="Tab Two">Content 2</ngc-tab>
      <ngc-tab name="Tab Three">Content 3</ngc-tab>
    </ngc-tabs>
  `
})
export class App {}

App

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

@Component({
  selector: 'ngc-tab',
  template: `
    <div *ngIf="active">
      <ng-content></ng-content>
    </div>
  `
})
export class Tab {
  @Input() name: string;

  active: boolean = false;
}

Tab

import {Component, ContentChildren, QueryList} from '@angular/core';
import {Tab} from './tab';

@Component({
  selector: 'ngc-tabs',
  template: `
    <div class="tab-buttons">
        <button *ngFor="let tab of tabs" [class.active]="tab.active"
                (click)="activateTab(tab)">{{tab.name}}</button>
    </div>
    <ng-content select="ngc-tab"></ng-content>
  `
})
export class Tabs {
  @ContentChildren(Tab) tabs: QueryList<Tab>;

  ngAfterContentInit() {
    this.activateTab(this.tabs.first);
  }

  activateTab(tab) {
    this.tabs.toArray().forEach((t) => t.active = false);
    tab.active = true;
  }
}

Tabs

Prefer Composition over Bloating

Prefer Composition over Bloating

<profile-image [user]="user"></profile-image>
<div class="user-name">{{user.name}}</div>
<div class="title" *ngIf="title">{{title}}</div>
<div class="preview">{{message | truncate:50}}</div>
<div class="tag" *ngFor="let tag of tags">{{tag}}</div>
<div class="time" *ngIf="time">{{time}}</div>

Message Item

<section *ngIf="!expanded">
  <profile-image [user]="user"></profile-image>
  <div class="user-name">{{user.name}}</div>
  <div class="title" *ngIf="title">{{title}}</div>
  <div class="preview">{{message | truncate:50}}</div>
  <div class="tag" *ngFor="let tag of tags">{{tag}}</div>
  <div class="time" *ngIf="time">{{time}}</div>
</section>
<section *ngIf="expanded">
  <header>
    <profile-image [user]="user"></profile-image>
    <div class="to">{{to}}</div>
    <div class="time" *ngIf="time">{{time}}</div>
    <button (click)="viewDetails()">View Details</button>
    <button (click)="reply()">Reply</button>
    <button (click)="forward()">Forward</button>
  </header>
  <div class="message">{{message}}</div>
  <button (click)="showQuoted()">Show quoted text</button>
</section>

Bloating the Message Item

Use Composition!

<message-item *ngIf="!expanded"></message-item>
<message-details *ngIf="expanded"></message-details>

Expandable Message Item

State & Data

@Component({
  selector: 'user-profile',
  template: `
    <p class="name">{{name}}</p>
  `
})
class UserProfile {
  constructor() {
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then((response) => response.json())
      .then((user) => this.name = user.name);
  }
}

Component with mixed concerns

Components connected to data lose Their composability

@Component({
  selector: 'user-profile',
  template: `
    <p class="name">{{name}}</p>
  `
})
class UserProfile {
  @Input() name;
}
//-------------------------------------------------------
@Component({
  selector: 'user-profile-container',
  template: `
    <user-profile [name]="user.name"></user-profile>
  `
})
class UserProfileContainer {
  constructor() {
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then((response) => response.json())
      .then((user) => this.user = user);
  }
}

Separate using Container Components

Data

Action

Container Component

Data

Action

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

@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<any>();
  
  remove() {
    this.onRemove.emit();
  }
}

List Item

@Component({
  selector: 'ngc-list',
  template: `
    <ngc-list-item *ngFor="let item of list; let i = index"
                   [item]="item" (onRemove)="removeItem(i)">
    </ngc-list-item>
  `
})
export class ListComponent {
  @Input() list: string[];
  @Output() onRemoveItem = new EventEmitter<number>();
  
  removeItem(index: number) {
    this.onRemoveItem.emit(index);
  }
}

List

@Component({
  selector: 'ngc-app',
  template: `
    <ngc-list [list]="list"
              (onRemoveItem)="removeListItem($event)">
    </ngc-list>
  `
})
export class MainComponent {
  list: string[] = ['Apples', 'Bananas', 'Strawberries'];
  
  removeListItem(index: number) {
    this.list = this.list.filter((e, i) => i !== index);
  }
}

App

Centralize your Data!

  • Re-use Components in different context

  • Clear responsibilities

  • Loose coupling with Input / Output

Change Detection

Asynchronous Zones using Zone.js

document.querySelector('button')
  .addEventListener('click', () => {
    setTimeout(() => {
      fetch('./data.json')
        .then((response) => response.text())
        .then((text) => {
          console.log(text);
        });
    }, 1000);
  });

-> Root Zone

-> Sub Zone 1

-> Sub Zone 2

-> Sub Zone 3

<- Sub Zone 3 End

<- Sub Zone 2 End

<- Sub Zone 1 End

Using Zones to Detect Changes

document.querySelector('button')
  .addEventListener('click', () => {
    setTimeout(() => {
      fetch('./data.json')
        .then((response) => response.text())
        .then((text) => {
          console.log(text));
        }
    }, 1000);
  });

-> Root Zone

-> Sub Zone 1

-> Sub Zone 2

-> Sub Zone 3

<- Sub Zone 3 End

<- Sub Zone 2 End

<- Sub Zone 1 End

Detect Changes

By Default Angular checks every binding of every Component on any event

Click Event

Default Change Detection

Pure Components

Pure components change, only if their input changes

@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<any>();
  
  remove() {
    this.onRemove.emit();
  }
}

Pure? Yes!

@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<any>();
  
  remove() {
    this.onRemove.emit();
  }
}

"OnPush" Change Detection Strategy

Click Event

Change Detection With Pure Components

Build Pure Components!

  • Performance Benefit

  • Simple to reason about

  • Easy to Reuse (no side effects)

Router

A Router serves three main purposes

  • Navigablity & Bookmarkability

  • UI Composition

  • Data

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

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

Include Router in Main Module

RouterModule.forRoot([{
  path: 'child1',
  component: Child1Component
}, {
  path: '',
  pathMatch: 'full',
  redirectTo: '/child1'
}, {
  path: '**',
  component: PageNotFoundComponent
}];

Redirects and Fallback Component

The router selects the route with a first match wins strategy.

<router-outlet></router-outlet>

The router outlet acts as a container for routed components to be loaded into.

Router Outlet 

<h1>Welcome!</h1>
<nav>
  <a routerLink="/child1" 
     routerLinkActive="active">child1</a>
  <a routerLink="/child2" 
     routerLinkActive="active">child2</a>
</nav>
<router-outlet></router-outlet>

Creating Navigation Links and Styling Active Links

RouterModule.forRoot([{
  path: 'child/:id',
  component: ChildComponent
}])

Route Configuration with Parameters

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

@Component({
  selector: 'ngc-child',
  template: 'Child:<p>Param: {{params.id}}</p>'
})
export class ChildComponent {
  constructor(@Inject(ActivatedRoute) route: ActivatedRoute) {
    this.params = route.snapshot.params;
  }
}

Accessing Parameters in Routed Components using Snapshot

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

import {Component, Inject} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'ngc-child',
  template: 'Child:<p>Param: {{(params | async).id}}</p>'
})
export class ChildComponent {
  constructor(@Inject(ActivatedRoute) route: ActivatedRoute) {
    this.params = route.params;
  }
}

Using Observable Params

Platform

Angular

UNIVERSAL

Angular runs where JavaScript runs!

Core Platforms

  • Browser

  • Web Worker

  • Server

Custom Platform

class MyRenderer implements Renderer {
  createElement(name: string): any {}
  createComment(value: string): any {}
  createText(value: string): any {}
  appendChild(parent: any, newChild: any): any {}
  removeChild(parent: any, oldChild: any): void {}
  setAttribute(node: any, name: string, value: string): void {}
  removeAttribute(node: any, name: string): void {}
  addClass(el: any, name: string): void {}
  removeClass(el: any, name: string): void {}
  ...
}

Implement Renderer

Let's Create a Canvas Graph Rendering Platform!

import {
  GraphBrowserModule, 
  platformGraphDynamic
} from './graph-platform';

@NgModule({
  imports: [GraphBrowserModule],
  declarations: [GrowingListComponent],
  bootstrap: [GrowingListComponent]
})
export class GrowingListModule {}

platformGraphDynamic().bootstrapModule(GrowingListModule);

Bootstrapping using Graph Platform

Conclusion

Go Angular!

  • Ease of Use / Development

  • Modular Out-of-the-Box Solution

  • Component Architecture

  • Standards

  • Universal / Multi-Platform

  • Performance

Thank You!

 Gion Kunz

 Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

Mastering Angular Components

By oddEVEN

Mastering Angular Components

  • 1,611