oddEVEN
We provide marketing technology, front end and software development consulting to bring your digital marketing and ecommerce projects to the next level.
Time | Topic |
---|---|
08:30 | Start |
10:00 | Break 15 min |
12:00 | Lunch Break |
13:00 | Continue |
14:30 | Break 15 min |
16:00 | Break 15 min |
17:30 | Finish |
Project Setup
Components
Data
Routing
Testing
Performance
You're all set to go :-)
Angular CLI
Angular Seed (by @mgechev)
Angular 2 Webpack Starter
Roll Your Own with JSPM
# Install Angular CLI
npm install -g @angular/cli
# Create new project
ng new oddeven-ng2-advanced --prefix oe
cd oddeven-ng2-advanced
# Create components with generator
ng generate component my-new-component
# Start dev server
ng serve --hmr
# Create production build
npm run build
# Clone Angular 2 Webpack Starter
git clone https://github.com/angularclass/angular2-webpack-starter.git
# Change directory
cd angular2-webpack-starter
# Install the repo with npm
npm install
# Start the server
npm start
# Install JSPM
npm install -g jspm
# Create new folder
mkdir angular-2-advanced
cd angular-2-advanced
# Initialize NPM project and install local JSPM
npm init
npm install --save-dev jspm
# Initialize JSPM and install the repo with npm
jspm init
jspm install npm:@angular/core npm:@angular/common ⏎
npm:@angular/compiler npm:@angular/platform-browser ⏎
npm:@angular/platform-browser-dynamic ⏎
npm:rxjs npm:zone.js npm:reflect-metadata
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
Artificial
Nature
Microcosm
Macrocosm
Fundamental
Significant
Pages
UI Elements
Arthur Koestler, The Ghost in the Machine (1967)
Holon
Holon
Holon
We need to build user interfaces like this...
...not like that
Should have Single Responsibility
Should be Simple
Should be Small
Should be Encapsulated
Should use Composition
Don't create components for reusability, create components for simplicity!
Getting the semantics right, is the first step to a good application design.
UI Semantics
Domain Semantics
Start with the smallest visible element
Try to apply UI Semantics
Try to apply Domain semantics
<application>
<navigation>
<user-profile></user-profile>
<navigation-group></navigation-group>
<navigation-group></navigation-group>
<navigation-group></navigation-group>
</navigation>
<main-content></main-content>
</application>
Application
Navigation
User Profile
User Image
Hard Boundary
Soft
Boundary
Soft semantic boundaries tend to increase / decrease scope depth.
Hard semantic boundaries tend to require different scope.
Application
Navigation
User Profile
User Image
@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);
}
}
Application
Container Component
User Profile
User Image
Navigation
Data
Update
Fix the "Clear List" button
Action
Dispatcher
Store
View
Redux
Flux
Elm
ngrx store
Redux
rxjs
Action
Reducer
Store
View
Current State and Action
Dispatch Action
New State
Rebuilding our TODO list using ngrx store
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;
}
}
RouterModule.forRoot([{
path: 'child',
component: ChildComponent,
children: [{
path: 'grand-child',
component: GrandChildComponent
}]
}])
constructor(@Inject(Router) private router: Router) {}
...
this.router.navigate(['/child1']);
<router-outlet></router-outlet>
<footer>
<router-outlet name="footer"></router-outlet>
</footer>
RouterModule.forRoot([
{path: 'child1', component: Child1Component},
{path: 'child2', component: Child2Component},
{path: 'child-aux', component ChildAuxComponent, outlet: 'footer'}
])
<a [routerLink]="[{outlets: {primary: ['child1'], footer: ['child-aux']}}]"></a>
<a [routerLink]="[{outlets: {footer: ['child-aux']}}]"></a>
<a [routerLink]="['/child', cId, 'grand-child', gcId]"></a>
Using Router DSL to construct URL segments /child/:id/grand-child/:id
Using Router DSL to load Auxiliary route into footer Outlet
Using Router DSL to load Auxiliary route and child into main / primary outlet
<a [routerLink]="[{outlets: {footer: null}}]"></a>
Clear Auxiliary outlet
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {Child1Component} from './child1.component';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{path: '', component: Child1Component}
])
],
declarations: [Child1Component]
})
export default class Child1Module {}
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {MainComponent} from './main.component';
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot([
{path: 'child1', loadChildren: './src/child1/child1.module'}
])
],
declarations: [MainComponent],
bootstrap: [MainComponent]
})
export class MainModule {}
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);
});
});
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
}
class DebugElement {
nativeElement: any
query(predicate: Predicate<DebugElement>): DebugElement
queryAll(predicate: Predicate<DebugElement>): DebugElement[]
children: DebugElement[]
triggerEventHandler(eventName: string, eventObj: any)
}
import {DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
const countElement = fixture.debugElement
.query(By.css('.count'))
.nativeElement;
console.log(countElement.textContent);
import {DebugElement} from '@angular/core';
const buttonDebugElement: DebugElement =
fixture.debugElement.query(By.css('button'));
buttonDebugElement.triggerEventHandler('click', null);
import {CounterService} from './counter.service';
export class CounterServiceMock() {
increment() {}
}
TestBed.configureTestingModule({
declarations: [MainComponent],
providers: [{
provide: CounterService,
useClass: CounterServiceMock
}]
});
Adding unit tests to our TODO app
Optimize Change Detection
Ahead of Time Compilation
Server Side Rendering (Angular Universal)
By oddEVEN