Building

Cross-Platform Apps using Redux with Angular 2

slides.com/gerardsans | @gerardsans

Google Developer Expert

Master of Ceremonies

International Speaker

Angular 2 Trainer

Community Leader

800

500

ngEurope - 25-26th Oct

@ngEurope

Angular 2

Yay! Released!

Killer Features

  • Latest Web Standards
  • Simple
  • Lightning fast
  • Works everywhere
  • Community

Angular Community

Ready for Production

  • Declarative Templating
  • Change Detection
  • Dependency Injection
  • Component Model
  • Ahead-of-Time Compilation
  • DOM Independence

ES5, ES6 and TypeScript

ES5 / ES6 / TypeScript

ES6 (ES2015)

  • Classes, modules, arrow functions

TypeScript

  • Types, decorators, generics, interfaces
  • Great editor support

Angular 2 IDEs

Developer Experience

Cross-platform

Web, Mobile and Desktop

New kids on the block

DESKTOP

MOBILE

Ionic

NativeScript

Electron

MacOS 10.9

Windows 7

Linux

iOS 7

Android 4.2

[Windows 10]

iOS 7

Android 4.1

Windows 8.1

Isomorphic Angular 2

Angular 2 Universal

Data Layer

DATA CLIENTS

GraphQL

Real-time

ngrx/store

Redux

STATE MANAGEMENT

Component Model

Components Tree

source: blog

Unidirectional Data Flow

source: blog

Component

  • @Component decorator
  • Communications
    • Inputs, @Input
    • Outputs, @Output
  • Component Lifecycle Hooks
  • Host element interaction

Component

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

@Component({
  selector:    'home', // <home></home>
  styles:      [`h1 { color: red }`],
  template:    `<h1>Home</h1>`
})
export class Home { ... }

Lifecycle Hooks

import { Component, OnChanges, OnInit, OnDestroy } from '@angular/core';

@Component()
export class myComponent implements OnChanges, OnInit, OnDestroy { 
  /* 1 */ constructor() { }

  // called when an input or output binding changes
  /* 2 */ ngOnChanges(changes) { }   
  
  // after child initialisation
  /* 3 */ ngOnInit() { } 
  
  // just before is destroyed
  /* 4 */ ngOnDestroy() { } 
}

Bootstrap

Overview

  • Angular Application instantiation
  • Root Module (AppModule) 
  • Application Context
    • RouterModule, HttpModule​
    • Components, directives, pipes
    • Services

index.html

<!DOCTYPE html>
<html>
  <head>
    <!-- Polyfill(s) for older browsers -->
    <script src="https://npmcdn.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js">
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    <script src="systemjs.config.js"></script>
    <script>System.import('app');</script>
  </head>
  <body>
    <my-app>
      Loading...
    </my-app>
  </body>
</html>

main.ts

import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';

platformBrowserDynamic().bootstrapModule(AppModule)

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { App } from './app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

app.component.ts

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

@Component({
  selector: 'my-app', // <my-app>Loading...</my-app>
  template: `...`
})
export class App { 
  constructor() { }
}

Demo time!

Templating

Template Syntax

Syntax Binding type
<h1>{{title}}</h1>
<input [value]="firstName">
<li [class.active]="isActive"></li>
<div [style.width.px]="mySize">
Interpolation
Property
Class
Style
<button (click)="onClick($event)"> Event
[(ngModel)]="data.value" Two-way

Redux

Dan Abramov

@gaearon

Main Principles

  • Unidirectional data flow
  • Single Immutable State
  • New states are created without side-effects

Unidirectional data flow

Single Immutable State

  • Helps tracking changes by reference
  • Improved Performance
  • Enforce by convention or using  a library. Eg: Immutable.js

Immutable by Convention

  • New array using Array Methods
    • map, filter, slice, concat
    • Spread operator (ES6) [...arr]
  • New object using Object.assign (ES6)

Using immutable.js

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

Reducers

  • Reducers create new states in response to Actions applied to the current State
  • Reducers are pure functions
  • Don't produce side-effects
  • Composable

Middlewares

  • Sit between Actions and Reducers
  • Used for logging, storage and asynchronous operations
  • Composable

Application Architecture

Demo time!

Components Tree

source: blog

Components Tree

<root>
  <add-todo>
    <input><button>Add todo</button>
  </add-todo>
  <todo-list>
    <ul>
      <todo id="0" completed="false"><li>buy milk</li></todo>
    </ul>
  </todo-list>
  <filters>
    Show: <filter-link><a>All</a><filter-link> ... 
  </filters>
</root>

Redux Setup

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

Adding a new Todo

  • Component subscribe to the Store
  • Component dispatches ADD_TODO action
  • Store executes rootReducer
  • Store notifies Component
  • View updates

Subscribing to the Store

@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_TODO Action

// add new todo
{
  type: ADD_TODO,
  id: 1,
  text: "learn redux",
  completed: false
}

todos Reducer

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'
// }

currentFilter Reducer

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
// }

rootReducer

import { combineReducers } from 'redux'

export const rootReducer = combineReducers({
  todos: todos,
  currentFilter: currentFilter
});

New State

{
  todos: [{
    id: 1,
    text: "learn redux",
    completed: false
  }],
  currentFilter: 'SHOW_ALL'
}

// {
//  todos: [], <-- we start with no todos
//  currentFilter: 'SHOW_ALL'
// }

Stateless Todo Component

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

Change Detection

source: blog

Change Detection

source: blog

Redux Dev Tools

Features

  • Save/Restore State
  • Live Debugging
  • Time travel
  • Dispatch Actions

Demo time!

Libraries

  • Angular 2 bindings for Redux
  • Built on top of Redux
  • Compatible w/ DevTools and existing ecosystem
  • Re-implementation of Redux on top Angular 2 and RxJS 5
  • ngrx suite: store, effects, router, db
  • Not compatible w/ DevTools and existing ecosystem

Why use Redux?

Some reasons

  • Simplified Development
  • Avoids complex dependencies
  • Great Performance
  • Developer Experience (Dev Tools)

Hvala!

Thanks

Building Cross-Platform Apps using Redux with Angular 2

By Gerard Sans

Building Cross-Platform Apps using Redux with Angular 2

Redux has proved very successful. Inspired by Flux and Elm, is used to handle Application state and bind it to the User Interface. Angular 2 has shifted from being just a framework to being a cross platform. This talk shows how everything fits together in this new paradigm.

  • 4,050