oddEVEN
We provide marketing technology, front end and software development consulting to bring your digital marketing and ecommerce projects to the next level.
By Gion Kunz
Build scalable UI Architectures using Angular Components
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
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;
}
}
<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>
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)
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);
@Component({
selector: 'app',
template: 'Hello World'
})
export class AppComponent {
constructor(@Inject(ElementRef) elementRef) {
elementRef.nativeElement
.setAttribute('data-message', 'Hey!');
}
}
@Component({
selector: 'app',
template: 'Hello World'
})
export class AppComponent {
constructor(@Inject(ElementRef) elementRef,
@Inject(Renderer) renderer) {
renderer.setAttribute(
elementRef.nativeElement,
'data-message',
'Hey!'
);
}
}
By oddEVEN