by Gerard Sans (@gerardsans)
Laptops charged and ready by 10:00~ish
Instructions bit.ly/ng2-advanced-doc
10:00 Morning block
11:00 Coffee break
13:00 Lunch break
14:00 Afternoon block
15:00 Coffee break
17:00 Wrapping up
1x 35% discount ng-book 2 (£13)
twit to participate in raffle
@angular_zone #ng2poznan
We are going to cover in this workshop
DATA CLIENTS
GraphQL
Real-time
ngrx/store
Redux
STATE MANAGEMENT
Dan Abramov
let selectedUsers = Immutable.List([1, 2, 3]);
let user = Immutable.Map({ id: 4, username: 'Spiderman'}):
let newSelection = selectedUsers.push(4, 5, 6); // [1, 2, 3, 4, 5, 6];
let newUser = user.set('admin', true);
newUser.get('admin') // true
import { App } from './app';
import { createStore } from 'redux';
const appStore = createStore(rootReducer);
@NgModule({
imports: [ BrowserModule ],
declarations: [
App, ...APP_DECLARATIONS
],
providers: [
{ provide: 'AppStore', useValue: appStore },
TodoActions
],
bootstrap: [ App ]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
@Component({
template:
`<todo *ngFor="let todo of todos">{{todo.text}}</todo>`
})
export class TodoList implements OnDestroy {
constructor(@Inject('AppStore') private appStore: AppStore){
this.unsubscribe = this.appStore.subscribe(() => {
let state = this.appStore.getState();
this.todos = state.todos;
});
}
private ngOnDestroy(){
this.unsubscribe();
}
}
// add new todo
{
type: ADD_TODO,
id: 1,
text: "learn redux",
completed: false
}
const todos = (state = [], action) => {
switch (action.type) {
case TodoActions.ADD_TODO:
return state.concat({
id: action.id,
text: action.text,
completed: action.completed });
default: return state;
}
}
// {
// todos: [], <-- todos reducer will mutate this key
// currentFilter: 'SHOW_ALL'
// }
const currentFilter = (state = 'SHOW_ALL', action) => {
switch (action.type) {
case 'SET_CURRENT_FILTER':
return action.filter
default: return state
}
}
// {
// todos: [],
// currentFilter: 'SHOW_ALL' <-- filter reducer will mutate this key
// }
import { combineReducers } from 'redux'
export const rootReducer = combineReducers({
todos: todos,
currentFilter: currentFilter
});
{
todos: [{
id: 1,
text: "learn redux",
completed: false
}],
currentFilter: 'SHOW_ALL'
}
// {
// todos: [], <-- we start with no todos
// currentFilter: 'SHOW_ALL'
// }
// <todo id="1" completed="true">buy milk</todo>
@Component({
inputs: ['id', 'completed'],
template: `
<li (click)="onTodoClick(id)"
[style.textDecoration]="completed?'line-through':'none'">
<ng-content></ng-content>
</li>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Todo {
constructor(
@Inject('AppStore') private appStore: AppStore,
private todoActions: TodoActions){ }
private onTodoClick(id){
this.appStore.dispatch(this.todoActions.toggleTodo(id));
}
}
// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { Users } from './users.component';
import { User } from './user.component';
const usersRoutes: Routes = [
{ path: 'users', component: Users },
{ path: 'users/:id', component: User }
];
export const UsersRouting = RouterModule.forChild(usersRoutes);
#/home
['home']
['users', 34] /users/:id
router.navigate(['users', 34])
router.navigateByUrl('/users/34')
import { Component } from '@angular/core';
@Component({
selector: 'users',
template: `
<h1>Users</h1>
<tr *ngFor="let user of users">
<td>
<a [routerLink]="['/users', user.id]">{{user.username}}</a>
</td>
</tr>
`
})
export class Users { }
// { path: 'users/:id', component: User } Url: #/users/34
import { Router } from '@angular/router';
@Component({
selector: 'user-details'
})
export class User implements OnInit {
constructor(private router: Router){ }
ngOnInit() {
console.log(this.router.url); // /users/34
}
}
// { path: 'users/:id', component: User } Url: #/users/34#section
import { Router } from '@angular/router';
@Component({
selector: 'user-details'
})
export class User implements OnInit {
constructor(private router: Router){
router.routerState.root.fragment.subscribe(f => {
let fragment = f; // section
});
}
ngOnInit() {
let fragment = this.router.routerState.snapshot.root.fragment; // section
}
}
// { path: 'users/:id', component: User } Url: #/users/34?q=1
import { Router } from '@angular/router';
@Component({
selector: 'user-details'
})
export class User implements OnInit {
constructor(private router: Router){
router.routerState.root.queryParams.subscribe(params => {
let q = params.q; // 1
});
}
ngOnInit() {
let q = this.router.routerState.snapshot.root.queryParams.q; // 1
}
}
// { path: 'users/:id', component: User, data: { key: 1 } }
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'user-details'
})
export class User implements OnInit {
constructor(private route: ActivatedRoute){
route.data.subscribe(data => {
let key = data.key; // 1
});
}
ngOnInit() {
let key = this.route.snapshot.data.key; // 1
}
}
// { path: 'users/:id', component: User } Url: #/users/34;flag=true
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'user-details'
})
export class User implements OnInit {
constructor(private route: ActivatedRoute){
route.params.subscribe(params => {
let id = params.id; // 34
});
}
ngOnInit() {
let id = this.route.snapshot.params.id; // 34
let flag = this.route.snapshot.params.flag; // true
}
}
// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { UserCanDeactivate } from './user.canDeactivate';
const usersRoutes: Routes = [
{ path: 'users', component: Users },
{ path: 'users/:id', component: User,
canDeactivate: [UserCanDeactivate]
}
];
export const UsersRouting = RouterModule.forChild(usersRoutes);
// user.canDeactivate.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
Injectable()
export class UserCanDeactivate implements CanDeactivate {
canDeactivate() {
return window.confirm('Do you want to continue?');
}
}
// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { AuthCanActivate } from './auth.canActivate';
const usersRoutes: Routes = [
{ path: 'users', component: Users },
{ path: 'users/:id', component: User,
canDeactivate: [UserCanDeactivate],
canActivate: [AuthCanActivate]
}
];
export const usersRouting = RouterModule.forChild(usersRoutes);
// auth.canActivate.ts
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { LoginService } from './login.service';
export class AuthCanActivate implements CanActivate {
constructor(private loginService: LoginService, private router: Router) {}
canActivate() {
if (!this.loginService.authorised) {
this.router.navigate(['/home']);
return false;
}
return true;
}
}
// app.routing.ts
const aboutRoutes: Routes = [
{ path: 'about', loadChildren: './app/about.module.ts' },
];
export const AboutRouting = RouterModule.forChild(aboutRoutes);
//about.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AboutRouting } from './about.routing';
import { About } from './about.component';
@NgModule({
imports: [ CommonModule, AboutRouting ],
declarations: [ About ]
})
export default class AboutModule { }
// app.routing.ts
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
const appRoutes: Routes = [
{ path: 'about', loadChildren: './app/about.module.ts' },
];
export const AppRouting = RouterModule.forRoot(appRoutes, {
useHash: true,
preloadingStrategy: PreloadAllModules
});
// app.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { CustomPreload } from './custom.preload';
const appRoutes: Routes = [
{ path: 'about', loadChildren: ..., data: { preload: true } },
];
export const AppRouting = RouterModule.forRoot(appRoutes, {
useHash: true,
preloadingStrategy: CustomPreload
});
// custom.preload.ts
import { PreloadingStrategy } from '@angular/router';
export class CustomPreload implements PreloadingStrategy {
preload(route: Route, preload: Function): Observable<any> {
return route.data && route.data.preload ? preload() : Observable.of(null);
}
}
// app.routing.ts
const routes: Routes = [
{path: 'home', component: HelloCmp},
{path: 'banner', component: Banner, outlet: 'bottom'}
];
// app.component.ts
@Component({
selector: 'my-app',
template: `
<router-outlet></router-outlet>
<router-outlet name="bottom"></router-outlet>`,
})
export class App { }
// templates/code
<a href="#/home(bottom:banner)">Show Banner!</a>
<a [routerLink]="['/home(bottom:banner)']">Show Banner!</a>
router.navigate('/home(bottom:banner)');
// about.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { About } from './about.component';
import { AboutResolver } from './about.resolver';
const aboutRoutes: Routes = [
{ path: '', component: About,
resolve: { magicNumber: AboutResolver }
}
];
export const AboutRouting = RouterModule.forChild(aboutRoutes);
// about.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
@Injectable()
export class AboutResolver implements Resolve {
resolve() {
console.log('Waiting to resolve...');
return new Promise(resolve => setTimeout(() => resolve(4545), 2000));
}
}
// about.component.ts
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'about',
template: `
<h1>About</h1>
<p>Magic Number: {{magicNumber}}</p>`
})
export class About {
magicNumber: number;
constructor(private route: ActivatedRoute){
this.magicNumber = route.snapshot.data.magicNumber;
}
}
import {ChangeDetectorRef} from '@angular/core'
@Component({ selector: 'dashboard', inputs: ['data'] })
export class DataDashboard {
constructor(private ref: ChangeDetectorRef) {
ref.detach();
setInterval(() => {
this.ref.detectChanges();
}, 5000);
}
}
ES5/6 Bundle
Change detection
JiT Compilation
V8 (Chrome)
Browser
Build
AoT Compilation
ES5/6 Bundle
Change Detection
V8 (Chrome)
Build
Browser
server
response
assets
download
client
init
client
data
render
server
response
assets
download
client
init
client
data
render
server view displayed
client
responsive
UI unresponsive
DESKTOP
MOBILE
Ionic 2
NativeScript
Electron
MacOS 10.9
Windows 7
Linux
iOS 7
Android 4.2
[Windows 10]
iOS 7
Android 4.1
Windows 8.1
> npm install -g typescript
> tsc --version
> tsc <file.ts> // -> <file.js> (ES5/6)
> tsc --sourcemap <file.ts> // -> sourcemaps
> tsc --t ES5 main.ts // transpile ES5 (ES3 default)
> tsc -w *.ts // watch mode
> tsc // will try to use local tsconfig.json with default setup
// languagesService.ts
export class LanguagesService {
get() {
return ['en', 'es', 'fr'];
}
}
// home.component.ts
import { LanguagesService } from '../services/languagesService';
class Home {
constructor(languages: LanguagesService) {
this.languages = languages.get();
}
}
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number {
return x+y;
};
let myAdd2 = (x: number, y: number): number => x+y;
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return `Hello, ${this.greeting}`;
}
}
let greeter = new Greeter("world");
var Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
}());
var greeter = new Greeter("world");
class Greeter {
constructor(private greeting: string) { }
}
class Greeter {
private greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// <textarea id="myElement" rows="4" cols="50">Some text</textarea>
var txt = (<HTMLTextAreaElement>document.getElementById("myElement")).value
let isDone: boolean = false; // boolean
let height: number = 6; // number
let name: string = "bob"; // string
let list: number[] = [1, 2, 3]; // arrays
let list: Array<number> = [1, 2, 3]; // generics
enum Color {Red, Green, Blue}; // enums
let c: Color = Color.Green;
let notSure: any = 4; // if not defined
let list: any[] = [1, true, "free"];
function warnUser(): void { // return type
alert('WAT?');
}
TS
// @Component({ selector: "my-app" }) class App { }
function Component(annotation: any): Function {
return (target: Function) => {
// add implementation here
};
}
//@AddTag('super-power') class SuperHero { name: string }
function AddTag(annotation: any): Function {
return (target: TFunction) => {
Reflect.defineMetadata("tag", annotation, target);
};
}
// class SuperHero {
// @Input('nickName') name: string;
// }
function Input(annotation: any): Function {
return (target: TFunction, key: string) => {
// add implementation here
}
}
// class SuperHero {
// constructor(@Log service: Service) { }
// constructor(@Log('my-service') service: Service) { }
// }
function Log(target: TFunction, key: string, index: number) {
// add implementation here
}
function Log(annotation: string) {
return (target: TFunction, key: string, index: number) => {
// add implementation here
}
}