oddEVEN
We provide marketing technology, front end and software development consulting to bring your digital marketing and ecommerce projects to the next level.
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
Getting Started
Why Components?
Angular by Example
Conclusion & Summary
Questions
Angular CLI
Angular Seed (by @mgechev)
Angular 2 Webpack Starter
Roll Your Own with JSPM & SystemJS
Herbert A. Simon, The Sciences of the Artificial (1969)
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...
Consists of ~1000 parts
Assembled of individual parts in one go
Fell apart completely, if he needed to answer calls
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...
Artificial
Nature
Microcosm
Macrocosm
Fundamental
Significant
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>
<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>
<label class="checkbox__label">
<input class="checkbox__input" type="checkbox"
[checked]="checked"
(change)="onCheckedChange($event.target.checked)">
<span class="checkbox__text">{{label}}</span>
</label>
Allows to Focus
Fitness / Ready for Change
Flexibility
Reduce atomistic Complexity
// 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 {}
@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
Module A
Module B
Module C
1
2
3
4
5
6
7
8
// 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 {}
import {platformBrowserDynamic}
from '@angular/platform-browser-dynamic';
import {AppModule} from './app-module';
platformBrowserDynamic().bootstrapModule(AppModule);
@Component({
selector: 'counter'
template: '<input type="text" [value]="num">'
})
class AppComponent {
num: number = 0;
constructor() {
setInterval(() => this.num++, 1000);
}
}
<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
@Component({
selector: 'app'
template: `
<button (click)="onClick()">Click me</button>
`
})
class AppComponent {
onClick() {
alert('Cool!');
}
}
<input (input)="handleInput($event.target.value)">
import {Input} from '@angular/core';
@Component({
selector: 'person',
template: '{{details.name}}'
})
export class PersonComponent {
@Input() details;
}
@Component({
selector: 'app',
template: '<person [details]="person"></person>'
})
export class AppComponent {
person = {
name: 'Gion'
};
}
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
);
}
}
@Component({
selector: 'app',
template: '<timer (timeout)="onTimeout($event)"></timer>'
})
export class AppComponent {
onTimeout(message) {
console.log(message);
}
}
Intrinsic
Extrinsic
@Component({
selector: 'jar',
template: '<ng-content></ng-content>'
})
export class Jar {}
@Component({
selector: 'app',
template: '<jar><p>Content</p></jar>'
})
export class 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 {}
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;
}
}
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 {}
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;
}
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;
}
}
<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 *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>
<message-item *ngIf="!expanded"></message-item>
<message-details *ngIf="expanded"></message-details>
@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({
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);
}
}
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();
}
}
@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);
}
}
@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);
}
}
Re-use Components in different context
Clear responsibilities
Loose coupling with Input / Output
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
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
Click Event
@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();
}
}
Click Event
Performance Benefit
Simple to reason about
Easy to Reuse (no side effects)
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 {}
RouterModule.forRoot([{
path: 'child1',
component: Child1Component
}, {
path: '',
pathMatch: 'full',
redirectTo: '/child1'
}, {
path: '**',
component: PageNotFoundComponent
}];
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.
<h1>Welcome!</h1>
<nav>
<a routerLink="/child1"
routerLinkActive="active">child1</a>
<a routerLink="/child2"
routerLinkActive="active">child2</a>
</nav>
<router-outlet></router-outlet>
RouterModule.forRoot([{
path: 'child/:id',
component: ChildComponent
}])
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;
}
}
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;
}
}
Browser
Web Worker
Server
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 {}
...
}
import {
GraphBrowserModule,
platformGraphDynamic
} from './graph-platform';
@NgModule({
imports: [GraphBrowserModule],
declarations: [GrowingListComponent],
bootstrap: [GrowingListComponent]
})
export class GrowingListModule {}
platformGraphDynamic().bootstrapModule(GrowingListModule);
Ease of Use / Development
Modular Out-of-the-Box Solution
Component Architecture
Standards
Universal / Multi-Platform
Performance
By oddEVEN