SlideShare a Scribd company logo
PREDICTABLE WEB APPS WITH
ANGULAR AND REDUX
@giorgionatili
ABOUT ME
✴ Engineering Manager at
Akamai Technologies
✴ Google Developer Expert
✴ Organizer of DroidconBos
(www.droidcon-boston.com)
✴ Organizer of SwiftFest 2017
(www.swiftfest.io)
✴ Founder of Mobile Tea 

(www.mobiletea.net)
Droidcon Boston
bitly.com/dcbos18
Droidcon Boston
bitly.com/dcbos18
Mobile Tea
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
AGENDA
✴ Using AngularJS within Angular applications
✴ Integrating AngularJS components into Angular
✴ Redux in a nutshell
✴ Predictable states in an hybrid world
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
THE PROBLEMS
✴ Multiple components and services consume and fetch data
from multiple sources at a not given time
✴ Multidirectional and optimistic data propagation
✴ Application state out of control and not testable
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
TOOLS WE’LL USE
+ +
&
ANGULARJS & ANGULAR
Using AngularJS within Angular applications
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
WORKING WITH HYBRID ANGULAR APPS
✴ Modules built with different Angular versions should work in
the same app
✴ The routing system(s) should be aware of which routes it
should take care of
✴ Existing components should work in the NG4 world
CONFIGURING THE APP
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
ANGULAR STYLE
✴ Angular apps are built as a tree of components
✴ Components are encapsulated, use@Inputs and @Outputs
to pass the data in & out
✴ Angular compiles components ahead of time as part of our
build process
✴ Angular is built on top of TypeScript and clearly separates
the static parts of our applications (stored in decorators) from
the dynamic parts (components)
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
ANGULARJS 1.5.X+ STYLE
✴ Yo can write AngularJS applications in the Angular style
✴ AngularJS 1.5+ added angular.component and the
$onInit(), $onDestroy(), and $onChanges() life-cycle
events
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
NGUPGRADE
✴ NgUpgrade is a library to use in our applications to mix and
match AngularJS and Angular components
✴ It bridges the AngularJS and Angular dependency injection
systems
✴ With NgUpgrade it’s possible to bootstrap an existing
AngularJS application from an Angular one
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
USING NGUPGRADE
@NgModule({ declarations: [
AppComponent
],
imports: [
BrowserModule,
UpgradeModule
] })
export class AppModule {
constructor(private upgrade: UpgradeModule) {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['AngularJsModule']);
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
UPGRADEMODULE.BOOTSTRAP
✴ It makes sure angular.bootstrap runs in the right zone
✴ It adds an extra module that sets up AngularJS to be visible in
Angular and vice versa
✴ It adapts the testability APIs to make Protractor work with
hybrid apps
@giorgionatili@DroidconBos
THAT’S IT…
@giorgionatili@DroidconBos
COMMON ISSUES
✴ It’s not possible to automatically upgrade components and
directives that use link or compile
✴ Components/directives that use scope can’t be upgraded too
✴ Also NgModel is not supported by NgUpgrade
@giorgionatili@DroidconBos
ANY OPTION?
@giorgionatili@DroidconBos
UPGRADE STRATEGIES
✴ Updating the app it route by route, screen by screen, or
feature by feature (vertical slicing)
✴ Start with upgrading reusable components like inputs and
date pickers (horizontal slicing)
@giorgionatili@DroidconBos
INTEGRATE THE TWO CODE BASES
ANGULARJS AND
ANGULAR
Integrating AngularJS components into Angular
@giorgionatili@DroidconBos
WEBPACK
✴ Most Angular 2/4 code seems to be using Webpack for
building
✴ Although it’s technically possible to use other solutions like
Browserify, it makes most sense to switch to Webpack to
avoid going "swimming upstream" as it were
@giorgionatili@DroidconBos
OTHER TASKS RUNNER
✴ Webpack creates essentially a single bundle which you then
use other plugins to split into separate files
✴ When there are tasks that didn't fit well into this model (unit
tests, merging of locale files, etc.) don’t migrate them but
keep them (i.e. Gulp)
@giorgionatili@DroidconBos
WEBPACK CONFIGURATION
✴ Create your configuration file by using $ eject from the
angular-cli tool
✴ Separate your configuration files for the development, testing
and production environments
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
WEBPACK CONFIG FILE
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var commonConfig = require('./webpack.common.js');
//add HMR client to entries
let entries = {};
for (let entryName of Object.keys(commonConfig.entry)) {
entries[entryName] = commonConfig.entry[entryName].concat('webpack-hot-middleware/client?
reload=true');
}
let merge = webpackMerge.strategy({entry: 'replace'});
module.exports = merge(commonConfig, {
devtool: 'cheap-module-inline-source-map',
entry: entries,
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
// enable HMR globally
new webpack.HotModuleReplacementPlugin(),
// prints more readable module names in the browser console on HMR updates
new webpack.NamedModulesPlugin()
],
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
});
@giorgionatili@DroidconBos
HYBRID APP TEMPLATE
Create two directives in your html, one is the Angular (i.e. my-
app) app, the other is the AngularJS app (i.e. myangularjs-
app-root)
<myangularjs-app-root></myangularjs-app-root>
<my-app></my-app>
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
BOOTSTRAPPING ANGULAR 4
✴ Angular is bootstrapped first through:
platformBrowserDynamic().bootstrapModule(AppMo
dule);
✴ In the app module explicitly say to use your AppComponent
to bootstrap the main component bootstrap:
[AppComponent]
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
BOOTSTRAPPING ANGULAR 1
In the constructor of the app module bootstrap AngularJS to
prevent the $injector not found error
constructor(private upgrade: UpgradeModule) {

upgrade.bootstrap(document.documentElement, ['angular-js-app'],
{strictDi: true});
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
INJECTING THE SCOPE
To be sure the scope is injected into Angular, use a provider to
simply get the $scope into the AppModule
const angularjsScopeProvider: FactoryProvider = {
provide: '$scope',
useFactory: ($injector: any) => $injector.get('$rootScope'),
deps: ['$injector']
};
@giorgionatili@DroidconBos
DUAL ROUTING
✴ It’s possible to run simultaneously different routing systems
✴ To get ui-router and the Angular one working together, it’s
enough to tell each router to ignore the other's routes
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
TELLING UI-ROUTER TO IGNORE OUR ANGULAR4 ROUTE
export class YourAppRootController {
constructor($rootScope, $location) {
'ngInject';
Object.assign(this, {$rootScope, $location});
this.ng1Route = true;
$rootScope.$on('$locationChangeSuccess',
this.handleStateChange.bind(this));
}
handleStateChange() {
// If the url starts with /ng4-route this will be handled by NG4 

// and we should hide the ui-view
this.ng1Route = !this.$location.url().startsWith(‘/ng4-route‘);
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
HIDING THE UI-VIEW WHEN NOT ON A NG1 ROUTE
export class AngularJSRootController {
constructor($rootScope, $location) {
'ngInject';
Object.assign(this, {$rootScope, $location});
this.ng1Route = true;
$rootScope.$on('$locationChangeSuccess',
this.handleStateChange.bind(this));
}
handleStateChange() {
//if the url starts with /ng4-route it should hide the ui-view)
this.ng1Route = !this.$location.url().startsWith(‘/ng4-route');
}
}
@giorgionatili@DroidconBos
ALMOST THERE
UPGRADING COMPONENTS
@giorgionatili@DroidconBos
UPGRADING ISSUES
✴ There are a few things that will prevent a directive or
component from being upgraded:
✴ link function
✴ scope
✴ Requiring NgModel or any other controller on the directive
itself (or a parent)
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
COMPONENT WRAPPER
✴ There's no support for components that use NgModel in
upgradecomponent as of today
✴ To work around this, a possible solution, is implementing a
wrapper component
@giorgionatili@DroidconBos
WRAPPER RESPONSIBILITIES
✴ Acting as a bridge and using ng-model to put values into the
old component
✴ Listening to the ng-change events and bubbling up values
from the wrapper in an EventEitter
✴ Exposing and using the Angular methods as needed
@giorgionatili@DroidconBos
WRAPPER AND FORMS
✴ Angular offers a great variety of Forms validators
✴ When a component doesn’t have its own validation, using a
dummy form in the wrapper expose all the available Angular
validators
@giorgionatili@DroidconBos
WRAPPER CONTROLLER
✴ This is a class to assist in writing a controller for a AngularJS
component that wraps something using NgModel
✴ It handles passing through validation etc.
✴ The Component must be wrapped in a ng-form with name
$ctrl.model to work properly
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
WRAPPING AN EXISTING COMPONENT
import {IAngularStatic} from 'angular';
import {AbstractNgModelWrapperController} from './abstract-ng-model-wrapper.controller';
declare var angular: IAngularStatic;
class YourWrapperController extends AbstractNgModelWrapperController {
private collection: any[];
public writeValue(obj: any): void {
this.collection = obj;
}
public handleChange() {
this.onChange(this.collection);
this.onBlur();
this.onValidatorChange();
}
}
const yourWrapperComponent = {
bindings: {
collection: '<'
},
controller: YourWrapperController,
template: `<ng-form name="$ctrl.form">
<your-legcy-selector ng-model="collection">
</your-legacy-selector>
</ng-form>`
};
angular.module('your-app-name').component('yourComponenWrapper', yourWrapperComponent);
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
NGMODELWRAPPERCONTROLLER
import {ControlValueAccessor, Validator} from '@angular/forms';
export abstract class NgModelWrapperController implements ControlValueAccessor,
Validator {
public isDisabled: boolean = false;
protected onChange: any;
protected onBlur: any;
protected onValidatorChange: () => void;
public abstract writeValue(obj: any): void;
public registerOnChange(fn: any): void {
this.onChange = fn;
}
public registerOnTouched(fn: any): void {
this.onBlur = fn;
}
public registerOnValidatorChange(fn: () => void): void {
this.onValidatorChange = fn;
}
}
@giorgionatili@DroidconBos
UPGRADING THE COMPONENT
✴ Creating an Angular directive that wraps the upgraded
component
✴ Expose the @Input() and @Output() needed to
communicate with the Angular “world”
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
UPGRADING THE COMPONENT TO A DIRECTIVE
import { Directive, ElementRef, Injector, Input } from '@angular/core';
import { NgModelWrapperUpgradeComponent } from './ng-model-wrapper-upgrade.component';
@Directive({
selector: ‘your-directive-selector‘
})
export class YourDirective extends NgModelWrapperUpgradeComponent {


@Input() public collection: any[];
constructor(elementRef: ElementRef, injector: Injector) {
super('yourComponentWrapper', elementRef, injector);
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
NGMODELWRAPPERUPGRADECOMPONENT
import {UpgradeComponent} from '@angular/upgrade/static';
import {AbstractControl, ControlValueAccessor, ValidationErrors, Validator} from '@angular/forms';
export abstract class NgModelWrapperUpgradeComponent extends UpgradeComponent
implements ControlValueAccessor, Validator {
public writeValue(obj: any): void {
return (this as any).controllerInstance.writeValue(obj);
}
public registerOnChange(fn: any): void {
return (this as any).controllerInstance.registerOnChange(fn);
}
public registerOnTouched(fn: any): void {
return (this as any).controllerInstance.registerOnTouched(fn);
}
public setDisabledState(isDisabled: boolean): void {
return (this as any).controllerInstance.setDisabledState(isDisabled);
}
public validate(c: AbstractControl): ValidationErrors {
return (this as any).controllerInstance.validate(c);
}
public registerOnValidatorChange(fn: () => void): void {
return (this as any).controllerInstance.registerOnValidatorChange(fn);
}
}
@giorgionatili@DroidconBos
RELATIONSHIPS OVERVIEW
NgModelWrapper

Controller
NgModelWrapperUpgr
adeComponent
YourComponent

WrapperController
YourNG4Directive
@giorgionatili@DroidconBos
ALL SET!!!
REDUX IN A NUTSHELL
Predictably and Composability
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
DEFINITION
Redux holds all the state of your application.
It doesn't let you change that state directly, but instead forces you to
describe changes as plain objects called “actions".
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
CORE CONCEPTS
✴ A global immutable application state
✴ Unidirectional data-flow
✴ Changes to state are made in pure functions, or reducers
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
REDUX BUILDING BLOCKS
✴ Store, a centralized representation of the state of an app
✴ Reducers, pure functions that return a new representation of
the state
✴ Actions, POJOs that represent a change in the state of the app
✴ Middleware, a chain-able function used to extend Redux
@giorgionatili@DroidconBos
ACTIONS IN A NUTSHELL
✴ Actions are payloads of information that send data to the
application store
✴ By convention, they have a type attribute that indicates what
kind of action we are performing
✴ Flux standard actions also have a payload attribute used to
propagate the data needed to determine the next state
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
ACTIONS CREATORS
✴ Action creators are function that create actions
✴ They do not dispatch actions to the store, making them
portable and easier to test as they have no side-effects
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
HOW AN ACTION LOOKS LIKE
{
type: 'LOADED',
payload: {
text: 'Do something.'
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
ACTIONS SERVICES
✴ Encapsulating related-actions dispatchers in the same file
promote code reuse and organization
✴ Making them @Injectable() allow to use them within the
dependency injection system of Angular
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
ACTIONS SERVICES
import { Injectable } from '@angular/core';
import { NgRedux } from 'ng2-redux';
import { UserProfileState } from '../model/profile-state';
@Injectable()
export class ProfileLoadingActions {
static LOADED: string = 'PROFILE_LOADED';
constructor(private ngRedux: NgRedux<UserProfileState>) {}
loaded(): void {
this.ngRedux.dispatch({ type: ProfileLoadingActions.LOADED });
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
REDUCERS AS PURE FUNCTIONS
✴ Reducers are pure functions that, given an input, return
always the same output as a fresh representation of a slice of
the app state
✴ When the store needs to know how an action changes the
state, it asks the reducers
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
HOW A REDUCER LOOKS LIKE
import { ProfileLoadingActions } from '../../actions/loading-actions';
import { INITIAL_STATE } from './loading-initial-state';
export function isLoadingReducer(state: boolean = INITIAL_STATE,
action: any): boolean {
switch (action.type) {
case ProfileLoadingActions.LOADED:
return true;
case ProfileLoadingActions.UPDATED:
return true;
case ProfileLoadingActions.UPDATE:
return false;
case ProfileLoadingActions.NOT_LOADED:
return false;
default:
return state;
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
DESIGNING THE STORE
✴ The store is not a specular copy of the application model
✴ The store represents the information needed to properly
render the UI elements of a web application
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
COMBINE REDUCERS
✴ The most common state shape for a Redux app is a POJO
containing slices of the model at each top-level key
✴ combineReducers takes an object full of slice reducer
functions, and returns a new reducer function
✴ It is a utility function to simplify the most common use case
of combining multiple reducers to describe the app state
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
COMBINING REDUCERS IN ACTION
import { loadingReducer as isLoading }
from './loading/loading.reducer.ts';
import { errorReducer as error }
from './error/error.reducer.ts';
export const rootReducer =
combineReducers({
isLoading,
error,
});
MORE ON ACTIONS
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
COMBINE ACTIONS
✴ Combining actions together can be a nice way to simplify the
application state
✴ Executing in sequence or in parallel multiple actions can help
the semantic of your code
✴ There is a middleware (redux-combine-actions) to easy
combine async actions and dispatch them
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
INTEGRATION
import { createStore, combineReducers, applyMiddleware } from 'redux';
import combineActionsMiddleware from 'redux-combine-actions';
let createStoreWithMiddleware =
applyMiddleware(combineActionsMiddleware)(createStore);
let rootReducer = combineReducers(reducers); // Store initialization
let rootStore = createStoreWithMiddleware(rootReducer);
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
USING REDUX-COMBINE-ACTIONS
export function addTodo(text) {
return { type: 'ADD_TODO', text };
}
export function increment() {
return { type: 'INCREMENT_COUNTER' };
}
// Combine "addTodo" and "increment" actions
export function addTodoAndIncrement({text}) {
return {
types: [
'COMBINED_ACTION_START',
'COMBINED_ACTION_SUCCESS',
'COMBINED_ACTION_ERROR'
],
// Pass actions in array
payload: [addTodo.bind(null, text), increment]
};
}
// Dispatch action
store.dispatch(addTodoAndIncrement({text:'Dispatch combined action'}));
ASYNCHRONOUS CODE
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
ASYNCHRONOUS CODE
✴ Redux manages only synchronous actions
✴ The web is based on asynchronous calls
@giorgionatili@DroidconBos
AND NOW WHAT?!?
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
MANAGING ASYNC CODE WITH REDUX
import { Component, OnInit } from '@angular/core';
import { WebApiPromiseService } from './profile.service';
@Component({
selector: 'user-profile',
templateUrl: './user-profile.component.html'
})
export class UserProfileComponent implements OnInit {
constructor(private myPromiseService: WebApiPromiseService
private profileActions: ProfileLoadingActions) {}
ngOnInit() {
this.myPromiseService
.getService('v1/user/profile')
.then(result => this.profileActions.loaded())
.catch(error => console.log(error));
}
}
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
REDUX THUNK
✴ A thunk is a function that wraps an expression to delay its
evaluation
✴ Redux-Thunk middleware allows to write action creators that
return a function instead of an action
✴ The thunk can be used to delay the dispatch of an action, or
to dispatch it only if a certain condition is met
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
INTEGRATION
import { createStore, combineReducers, applyMiddleware } from 'redux';
import combineActionsMiddleware from 'redux-combine-actions';
import thunk from 'redux-thunk';
let createStoreWithMiddleware = applyMiddleware(thunk)
(combineActionsMiddleware)(createStore);
let rootReducer = combineReducers(reducers); // Store initialization
let rootStore = createStoreWithMiddleware(rootReducer);
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
REDUX THUNK AND PROMISES
function fetchSecretSauce(): Promise {
return fetch('https://www.google.com/search?q=secret+sauce');
}
function apologize(fromPerson: string, toPerson: string, error: any) {
return { type: 'APOLOGIZE', fromPerson, toPerson, error };
}
function makeSandwich(forPerson: string, secretSauce: string) {
return { type: 'MAKE_SANDWICH', forPerson, secretSauce };
}
function makeASandwichWithSecretSauce(forPerson: string) {
return function (dispatch: any) {
return fetchSecretSauce().then(
sauce => dispatch(makeSandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
// In your action service
store.dispatch(makeASandwichWithSecretSauce('Me'));
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
WHAT’S HAPPENED
✴ A function that accepts dispatch as argument get returned
so that dispatch can be called later
✴ The thunk middleware transformed thunk async actions into
actions
DETECT STATE CHANGES
@giorgionatili@DroidconBos
THE SELECT PATTERN
✴ The select pattern allows you to get slices of your state as
RxJS observables
✴ The @select decorator can be added to the property of any
class or Angular component/injectable
✴ It will turn the property into an observable which observes
the Redux Store value which is selected by the decorator's
parameter
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
SELECTORS
// Selects `counter` from the store and attaches it to this property
@select() counter;
// Selects `counter` from the store and attaches it to this property
@select('counter') counterSelectedWithString;
// Selects `pathDemo.foo.bar` from the store and attaches it the property
@select(['pathDemo', 'foo', 'bar']) pathSelection;
// This selects `counter` from the store and attaches it to this property
@select(state => state.counter) counterSelectedWithFunction;
// Selects `counter` from the store and multiples it by two
@select(state => state.counter * 2)
counterSelectedWithFuntionAndMultipliedByTwo: Observable<any>;
@giorgionatili@DroidconBos
REUSABLE SELECTORS
✴ A reusable selector is a function that return the selector
✴ By using reusable selectors it’s possible to minimize the
refactoring effort
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
FRACTAL STORES
✴ A subStore expose the same interface as the main Redux
store (dispatch, select, etc.)
✴ A subStore is rooted at a particular path in your global state
✴ A subStore is used in components that have instance-
specific access to Redux features
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
IMPLEMENTATION
export const userComponentReducer = (state, action) =>
action.type === 'ADD_LOCATION' ?
{ ...state, location: state.location + action.payload } : state;
export class UserComponent implements NgOnInit {
name: Observable<string>;
occupation: Observable<string>;
location: Observable<string>;
private subStore: ObservableStore<User>;
constructor(private ngRedux: NgRedux<UserProfileState>) {}
onInit() {
this.subStore = this.ngRedux.configureSubStore(
['users', userId],
userComponentReducer);
// Substore selectors are scoped to the base path used to configure the substore
this.name = this.subStore.select('name');
this.occupation = this.subStore.select('occupation');
this.location = this.subStore.select(s => s.location || 0);
}
@giorgionatili@DroidconBos
PREDICTABLE STATE CONTAINERS ARE SIMPLE
PREDICTABLE STATES
In an hybrid world
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
QUICK RECAP
✴ AngularJS and Angular can coexist in the same code base
✴ It’s possible to use ui-router and the Angular routing system
together
✴ Redux is a tool to support architectures with a centralized,
independent and decouple state
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
APPLICATION LAYER
Root app Bootstrap AngularJS and Angular
Initialize the store and the initial state
Angular
route
Actions and
creators
Define the DSL of the application
Decouple the creation of the actions as injectable services
Manipulate the application stateReducers
Propagate the state of the application as an immutable objectReducers
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
COMPONENTS
✴ Responsible of defining a node in your application three
✴ Responsible of fulfilling a single use case of the application
✴ Containing container components and legacy components
✴ Connecting legacy and container components with
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
CONTAINER COMPONENTS
✴ May contain both presentational and container components
✴ Markup is minimal and mostly contains presentation
components
✴ Are stateless because represent the datasource of presentation
components
✴ Use actions to communicate the intent of changing the state
✴ Listen to slices of the state updates with selectors
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
PRESENTATION COMPONENTS
✴ Have no dependencies on the rest of the app (i.e. actions)
✴ They don’t specify how the data is loaded or mutated
✴ Receive data and callbacks exclusively via @Input()
✴ Are written as functional components with no lifecycle hooks
✴ Communicate with parents through @Output()
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
STATE CHANGE
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
FIVE THINGS YOU SHOULD DO WHEN USING REDUX
✴ Design the application state before starting to code
✴ Build independent and self-contained modules using fractal
stores
✴ Implement action creators to don't repeat yourself
✴ Use filters and selectors to listen to changes of specific slices
✴ Remove as much as possible the logic from the components
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
FIVE THINGS YOU SHOULDN’T DO WHEN USING REDUX
✴ Store in the application state redundant information
✴ Pollute and make the DSL ambiguous with not needed actions
✴ Use Redux as an event bus system
✴ Apply optimistic changes when interacting with external API
✴ Create complex reducers instead of splitting the state
@giorgionatili | @theSwiftFest | @DroidconBos#webu17
PERFORMANCES HINT: RESELECT
✴ Selectors can compute derived data, allowing Redux to store
the minimal possible state
✴ A selector is not recomputed unless one of its arguments
change
✴ Selectors are composable can be used as input to other
selectors
QUESTIONS
and potentially answers :)
““The cleaner and nicer the program, the
faster it's going to run. And if it doesn't,
it'll be easy to make it fast.”
- Joshua Bloch
THANKS!
@giorgionatili




More Related Content

PDF
A gently introduction to AngularJS
PDF
GlobalLogic Test Automation Online TechTalk “Playwright — A New Hope”
PDF
AngularJS Project Setup step-by- step guide - RapidValue Solutions
PDF
How to Build ToDo App with Vue 3 + TypeScript
PDF
Using ReactJS in AngularJS
PDF
jQuery plugin & testing with Jasmine
PDF
Top 7 Angular Best Practices to Organize Your Angular App
PDF
React Native
A gently introduction to AngularJS
GlobalLogic Test Automation Online TechTalk “Playwright — A New Hope”
AngularJS Project Setup step-by- step guide - RapidValue Solutions
How to Build ToDo App with Vue 3 + TypeScript
Using ReactJS in AngularJS
jQuery plugin & testing with Jasmine
Top 7 Angular Best Practices to Organize Your Angular App
React Native

What's hot (20)

PDF
Node.js and Selenium Webdriver, a journey from the Java side
PDF
React Nativeの光と闇
PDF
Angular js
PDF
Spring MVC Intro / Gore - Nov NHJUG
PDF
Modularisation avec Gradle et Dagger
PDF
Introduction to React for Frontend Developers
KEY
BlackBerry DevCon 2011 - PhoneGap and WebWorks
PPT
Angular App Presentation
PDF
How to React Native
ODP
AngularJs Crash Course
PDF
React Native Androidはなぜ動くのか
PDF
Fullstack End-to-end test automation with Node.js, one year later
PDF
Introduction of React.js
PPTX
PDF
Vaadin Components
PDF
Overview of the AngularJS framework
KEY
Introducing PhoneGap to SproutCore 2
PDF
125 고성능 web view-deview 2013 발표 자료_공유용
PDF
AngularJS Basics and Best Practices - CC FE &UX
PDF
GWTcon 2015 - Beyond GWT 3.0 Panic
Node.js and Selenium Webdriver, a journey from the Java side
React Nativeの光と闇
Angular js
Spring MVC Intro / Gore - Nov NHJUG
Modularisation avec Gradle et Dagger
Introduction to React for Frontend Developers
BlackBerry DevCon 2011 - PhoneGap and WebWorks
Angular App Presentation
How to React Native
AngularJs Crash Course
React Native Androidはなぜ動くのか
Fullstack End-to-end test automation with Node.js, one year later
Introduction of React.js
Vaadin Components
Overview of the AngularJS framework
Introducing PhoneGap to SproutCore 2
125 고성능 web view-deview 2013 발표 자료_공유용
AngularJS Basics and Best Practices - CC FE &UX
GWTcon 2015 - Beyond GWT 3.0 Panic
Ad

Similar to Predictable Web Apps with Angular and Redux (20)

PPTX
AngularJS with TypeScript and Windows Azure Mobile Services
PDF
Angular2 workshop
PDF
Angular js
PDF
Angular js
PDF
Myths of Angular 2: What Angular Really Is
PDF
Building scalable applications with angular js
PPTX
Migrating from AngularJS when you can't use the word "Big Bang@
PPTX
AngularJS 1.x - your first application (problems and solutions)
PDF
Angular, the New Angular JS
PDF
AngularJS best-practices
PPTX
Angular js workshop
PPTX
Migrating From Angular 1.x to Angular 2+
PPTX
Angular%201%20to%20angular%202
PPT
AngularJS for Legacy Apps
PPTX
Walk in the shoe of angular
PDF
Angular Up and Running Learning Angular Step by Step 1st Edition Shyam Seshadri
PDF
The Angular road from 1.x to 2.0
PPTX
Angular TS(typescript)
PPTX
AngularJS Introduction (Talk given on Aug 5 2013)
PDF
Angular JS 2_0 BCS CTO_in_Res V3
AngularJS with TypeScript and Windows Azure Mobile Services
Angular2 workshop
Angular js
Angular js
Myths of Angular 2: What Angular Really Is
Building scalable applications with angular js
Migrating from AngularJS when you can't use the word "Big Bang@
AngularJS 1.x - your first application (problems and solutions)
Angular, the New Angular JS
AngularJS best-practices
Angular js workshop
Migrating From Angular 1.x to Angular 2+
Angular%201%20to%20angular%202
AngularJS for Legacy Apps
Walk in the shoe of angular
Angular Up and Running Learning Angular Step by Step 1st Edition Shyam Seshadri
The Angular road from 1.x to 2.0
Angular TS(typescript)
AngularJS Introduction (Talk given on Aug 5 2013)
Angular JS 2_0 BCS CTO_in_Res V3
Ad

More from FITC (20)

PPTX
Cut it up
PDF
Designing for Digital Health
PDF
Profiling JavaScript Performance
PPTX
Surviving Your Tech Stack
PDF
How to Pitch Your First AR Project
PDF
Start by Understanding the Problem, Not by Delivering the Answer
PDF
Cocaine to Carrots: The Art of Telling Someone Else’s Story
PDF
Everyday Innovation
PDF
HyperLight Websites
PDF
Everything is Terrifying
PDF
Post-Earth Visions: Designing for Space and the Future Human
PDF
The Rise of the Creative Social Influencer (and How to Become One)
PDF
East of the Rockies: Developing an AR Game
PDF
Creating a Proactive Healthcare System
PDF
World Transformation: The Secret Agenda of Product Design
PDF
The Power of Now
PDF
High Performance PWAs
PDF
Rise of the JAMstack
PDF
From Closed to Open: A Journey of Self Discovery
PDF
Projects Ain’t Nobody Got Time For
Cut it up
Designing for Digital Health
Profiling JavaScript Performance
Surviving Your Tech Stack
How to Pitch Your First AR Project
Start by Understanding the Problem, Not by Delivering the Answer
Cocaine to Carrots: The Art of Telling Someone Else’s Story
Everyday Innovation
HyperLight Websites
Everything is Terrifying
Post-Earth Visions: Designing for Space and the Future Human
The Rise of the Creative Social Influencer (and How to Become One)
East of the Rockies: Developing an AR Game
Creating a Proactive Healthcare System
World Transformation: The Secret Agenda of Product Design
The Power of Now
High Performance PWAs
Rise of the JAMstack
From Closed to Open: A Journey of Self Discovery
Projects Ain’t Nobody Got Time For

Recently uploaded (20)

PDF
Sims 4 Historia para lo sims 4 para jugar
PPTX
international classification of diseases ICD-10 review PPT.pptx
PPTX
Introuction about WHO-FIC in ICD-10.pptx
PPT
Design_with_Watersergyerge45hrbgre4top (1).ppt
PDF
SASE Traffic Flow - ZTNA Connector-1.pdf
PDF
Unit-1 introduction to cyber security discuss about how to secure a system
PDF
Paper PDF World Game (s) Great Redesign.pdf
PPTX
Internet___Basics___Styled_ presentation
PDF
Slides PDF The World Game (s) Eco Economic Epochs.pdf
PPTX
Introduction to Information and Communication Technology
PPTX
introduction about ICD -10 & ICD-11 ppt.pptx
PPTX
Digital Literacy And Online Safety on internet
PPTX
Slides PPTX World Game (s) Eco Economic Epochs.pptx
PDF
APNIC Update, presented at PHNOG 2025 by Shane Hermoso
PDF
RPKI Status Update, presented by Makito Lay at IDNOG 10
PPTX
INTERNET------BASICS-------UPDATED PPT PRESENTATION
PPTX
Job_Card_System_Styled_lorem_ipsum_.pptx
PDF
The Internet -By the Numbers, Sri Lanka Edition
PPTX
innovation process that make everything different.pptx
PPTX
QR Codes Qr codecodecodecodecocodedecodecode
Sims 4 Historia para lo sims 4 para jugar
international classification of diseases ICD-10 review PPT.pptx
Introuction about WHO-FIC in ICD-10.pptx
Design_with_Watersergyerge45hrbgre4top (1).ppt
SASE Traffic Flow - ZTNA Connector-1.pdf
Unit-1 introduction to cyber security discuss about how to secure a system
Paper PDF World Game (s) Great Redesign.pdf
Internet___Basics___Styled_ presentation
Slides PDF The World Game (s) Eco Economic Epochs.pdf
Introduction to Information and Communication Technology
introduction about ICD -10 & ICD-11 ppt.pptx
Digital Literacy And Online Safety on internet
Slides PPTX World Game (s) Eco Economic Epochs.pptx
APNIC Update, presented at PHNOG 2025 by Shane Hermoso
RPKI Status Update, presented by Makito Lay at IDNOG 10
INTERNET------BASICS-------UPDATED PPT PRESENTATION
Job_Card_System_Styled_lorem_ipsum_.pptx
The Internet -By the Numbers, Sri Lanka Edition
innovation process that make everything different.pptx
QR Codes Qr codecodecodecodecocodedecodecode

Predictable Web Apps with Angular and Redux

  • 1. PREDICTABLE WEB APPS WITH ANGULAR AND REDUX @giorgionatili
  • 2. ABOUT ME ✴ Engineering Manager at Akamai Technologies ✴ Google Developer Expert ✴ Organizer of DroidconBos (www.droidcon-boston.com) ✴ Organizer of SwiftFest 2017 (www.swiftfest.io) ✴ Founder of Mobile Tea 
 (www.mobiletea.net)
  • 6. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 AGENDA ✴ Using AngularJS within Angular applications ✴ Integrating AngularJS components into Angular ✴ Redux in a nutshell ✴ Predictable states in an hybrid world
  • 7. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 THE PROBLEMS ✴ Multiple components and services consume and fetch data from multiple sources at a not given time ✴ Multidirectional and optimistic data propagation ✴ Application state out of control and not testable
  • 8. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 TOOLS WE’LL USE + + &
  • 9. ANGULARJS & ANGULAR Using AngularJS within Angular applications
  • 10. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 WORKING WITH HYBRID ANGULAR APPS ✴ Modules built with different Angular versions should work in the same app ✴ The routing system(s) should be aware of which routes it should take care of ✴ Existing components should work in the NG4 world
  • 12. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ANGULAR STYLE ✴ Angular apps are built as a tree of components ✴ Components are encapsulated, use@Inputs and @Outputs to pass the data in & out ✴ Angular compiles components ahead of time as part of our build process ✴ Angular is built on top of TypeScript and clearly separates the static parts of our applications (stored in decorators) from the dynamic parts (components)
  • 13. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ANGULARJS 1.5.X+ STYLE ✴ Yo can write AngularJS applications in the Angular style ✴ AngularJS 1.5+ added angular.component and the $onInit(), $onDestroy(), and $onChanges() life-cycle events
  • 14. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 NGUPGRADE ✴ NgUpgrade is a library to use in our applications to mix and match AngularJS and Angular components ✴ It bridges the AngularJS and Angular dependency injection systems ✴ With NgUpgrade it’s possible to bootstrap an existing AngularJS application from an Angular one
  • 15. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 USING NGUPGRADE @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, UpgradeModule ] }) export class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { this.upgrade.bootstrap(document.body, ['AngularJsModule']); } }
  • 16. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 UPGRADEMODULE.BOOTSTRAP ✴ It makes sure angular.bootstrap runs in the right zone ✴ It adds an extra module that sets up AngularJS to be visible in Angular and vice versa ✴ It adapts the testability APIs to make Protractor work with hybrid apps
  • 18. @giorgionatili@DroidconBos COMMON ISSUES ✴ It’s not possible to automatically upgrade components and directives that use link or compile ✴ Components/directives that use scope can’t be upgraded too ✴ Also NgModel is not supported by NgUpgrade
  • 20. @giorgionatili@DroidconBos UPGRADE STRATEGIES ✴ Updating the app it route by route, screen by screen, or feature by feature (vertical slicing) ✴ Start with upgrading reusable components like inputs and date pickers (horizontal slicing)
  • 23. @giorgionatili@DroidconBos WEBPACK ✴ Most Angular 2/4 code seems to be using Webpack for building ✴ Although it’s technically possible to use other solutions like Browserify, it makes most sense to switch to Webpack to avoid going "swimming upstream" as it were
  • 24. @giorgionatili@DroidconBos OTHER TASKS RUNNER ✴ Webpack creates essentially a single bundle which you then use other plugins to split into separate files ✴ When there are tasks that didn't fit well into this model (unit tests, merging of locale files, etc.) don’t migrate them but keep them (i.e. Gulp)
  • 25. @giorgionatili@DroidconBos WEBPACK CONFIGURATION ✴ Create your configuration file by using $ eject from the angular-cli tool ✴ Separate your configuration files for the development, testing and production environments
  • 26. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 WEBPACK CONFIG FILE var webpack = require('webpack'); var webpackMerge = require('webpack-merge'); var commonConfig = require('./webpack.common.js'); //add HMR client to entries let entries = {}; for (let entryName of Object.keys(commonConfig.entry)) { entries[entryName] = commonConfig.entry[entryName].concat('webpack-hot-middleware/client? reload=true'); } let merge = webpackMerge.strategy({entry: 'replace'}); module.exports = merge(commonConfig, { devtool: 'cheap-module-inline-source-map', entry: entries, plugins: [ new webpack.NoEmitOnErrorsPlugin(), // enable HMR globally new webpack.HotModuleReplacementPlugin(), // prints more readable module names in the browser console on HMR updates new webpack.NamedModulesPlugin() ], devServer: { historyApiFallback: true, stats: 'minimal' } });
  • 27. @giorgionatili@DroidconBos HYBRID APP TEMPLATE Create two directives in your html, one is the Angular (i.e. my- app) app, the other is the AngularJS app (i.e. myangularjs- app-root) <myangularjs-app-root></myangularjs-app-root> <my-app></my-app>
  • 28. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 BOOTSTRAPPING ANGULAR 4 ✴ Angular is bootstrapped first through: platformBrowserDynamic().bootstrapModule(AppMo dule); ✴ In the app module explicitly say to use your AppComponent to bootstrap the main component bootstrap: [AppComponent]
  • 29. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 BOOTSTRAPPING ANGULAR 1 In the constructor of the app module bootstrap AngularJS to prevent the $injector not found error constructor(private upgrade: UpgradeModule) {
 upgrade.bootstrap(document.documentElement, ['angular-js-app'], {strictDi: true}); }
  • 30. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 INJECTING THE SCOPE To be sure the scope is injected into Angular, use a provider to simply get the $scope into the AppModule const angularjsScopeProvider: FactoryProvider = { provide: '$scope', useFactory: ($injector: any) => $injector.get('$rootScope'), deps: ['$injector'] };
  • 31. @giorgionatili@DroidconBos DUAL ROUTING ✴ It’s possible to run simultaneously different routing systems ✴ To get ui-router and the Angular one working together, it’s enough to tell each router to ignore the other's routes
  • 32. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 TELLING UI-ROUTER TO IGNORE OUR ANGULAR4 ROUTE export class YourAppRootController { constructor($rootScope, $location) { 'ngInject'; Object.assign(this, {$rootScope, $location}); this.ng1Route = true; $rootScope.$on('$locationChangeSuccess', this.handleStateChange.bind(this)); } handleStateChange() { // If the url starts with /ng4-route this will be handled by NG4 
 // and we should hide the ui-view this.ng1Route = !this.$location.url().startsWith(‘/ng4-route‘); } }
  • 33. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 HIDING THE UI-VIEW WHEN NOT ON A NG1 ROUTE export class AngularJSRootController { constructor($rootScope, $location) { 'ngInject'; Object.assign(this, {$rootScope, $location}); this.ng1Route = true; $rootScope.$on('$locationChangeSuccess', this.handleStateChange.bind(this)); } handleStateChange() { //if the url starts with /ng4-route it should hide the ui-view) this.ng1Route = !this.$location.url().startsWith(‘/ng4-route'); } }
  • 36. @giorgionatili@DroidconBos UPGRADING ISSUES ✴ There are a few things that will prevent a directive or component from being upgraded: ✴ link function ✴ scope ✴ Requiring NgModel or any other controller on the directive itself (or a parent)
  • 37. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 COMPONENT WRAPPER ✴ There's no support for components that use NgModel in upgradecomponent as of today ✴ To work around this, a possible solution, is implementing a wrapper component
  • 38. @giorgionatili@DroidconBos WRAPPER RESPONSIBILITIES ✴ Acting as a bridge and using ng-model to put values into the old component ✴ Listening to the ng-change events and bubbling up values from the wrapper in an EventEitter ✴ Exposing and using the Angular methods as needed
  • 39. @giorgionatili@DroidconBos WRAPPER AND FORMS ✴ Angular offers a great variety of Forms validators ✴ When a component doesn’t have its own validation, using a dummy form in the wrapper expose all the available Angular validators
  • 40. @giorgionatili@DroidconBos WRAPPER CONTROLLER ✴ This is a class to assist in writing a controller for a AngularJS component that wraps something using NgModel ✴ It handles passing through validation etc. ✴ The Component must be wrapped in a ng-form with name $ctrl.model to work properly
  • 41. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 WRAPPING AN EXISTING COMPONENT import {IAngularStatic} from 'angular'; import {AbstractNgModelWrapperController} from './abstract-ng-model-wrapper.controller'; declare var angular: IAngularStatic; class YourWrapperController extends AbstractNgModelWrapperController { private collection: any[]; public writeValue(obj: any): void { this.collection = obj; } public handleChange() { this.onChange(this.collection); this.onBlur(); this.onValidatorChange(); } } const yourWrapperComponent = { bindings: { collection: '<' }, controller: YourWrapperController, template: `<ng-form name="$ctrl.form"> <your-legcy-selector ng-model="collection"> </your-legacy-selector> </ng-form>` }; angular.module('your-app-name').component('yourComponenWrapper', yourWrapperComponent);
  • 42. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 NGMODELWRAPPERCONTROLLER import {ControlValueAccessor, Validator} from '@angular/forms'; export abstract class NgModelWrapperController implements ControlValueAccessor, Validator { public isDisabled: boolean = false; protected onChange: any; protected onBlur: any; protected onValidatorChange: () => void; public abstract writeValue(obj: any): void; public registerOnChange(fn: any): void { this.onChange = fn; } public registerOnTouched(fn: any): void { this.onBlur = fn; } public registerOnValidatorChange(fn: () => void): void { this.onValidatorChange = fn; } }
  • 43. @giorgionatili@DroidconBos UPGRADING THE COMPONENT ✴ Creating an Angular directive that wraps the upgraded component ✴ Expose the @Input() and @Output() needed to communicate with the Angular “world”
  • 44. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 UPGRADING THE COMPONENT TO A DIRECTIVE import { Directive, ElementRef, Injector, Input } from '@angular/core'; import { NgModelWrapperUpgradeComponent } from './ng-model-wrapper-upgrade.component'; @Directive({ selector: ‘your-directive-selector‘ }) export class YourDirective extends NgModelWrapperUpgradeComponent { 
 @Input() public collection: any[]; constructor(elementRef: ElementRef, injector: Injector) { super('yourComponentWrapper', elementRef, injector); } }
  • 45. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 NGMODELWRAPPERUPGRADECOMPONENT import {UpgradeComponent} from '@angular/upgrade/static'; import {AbstractControl, ControlValueAccessor, ValidationErrors, Validator} from '@angular/forms'; export abstract class NgModelWrapperUpgradeComponent extends UpgradeComponent implements ControlValueAccessor, Validator { public writeValue(obj: any): void { return (this as any).controllerInstance.writeValue(obj); } public registerOnChange(fn: any): void { return (this as any).controllerInstance.registerOnChange(fn); } public registerOnTouched(fn: any): void { return (this as any).controllerInstance.registerOnTouched(fn); } public setDisabledState(isDisabled: boolean): void { return (this as any).controllerInstance.setDisabledState(isDisabled); } public validate(c: AbstractControl): ValidationErrors { return (this as any).controllerInstance.validate(c); } public registerOnValidatorChange(fn: () => void): void { return (this as any).controllerInstance.registerOnValidatorChange(fn); } }
  • 48. REDUX IN A NUTSHELL Predictably and Composability
  • 49. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 DEFINITION Redux holds all the state of your application. It doesn't let you change that state directly, but instead forces you to describe changes as plain objects called “actions".
  • 50. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 CORE CONCEPTS ✴ A global immutable application state ✴ Unidirectional data-flow ✴ Changes to state are made in pure functions, or reducers
  • 51. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 REDUX BUILDING BLOCKS ✴ Store, a centralized representation of the state of an app ✴ Reducers, pure functions that return a new representation of the state ✴ Actions, POJOs that represent a change in the state of the app ✴ Middleware, a chain-able function used to extend Redux
  • 52. @giorgionatili@DroidconBos ACTIONS IN A NUTSHELL ✴ Actions are payloads of information that send data to the application store ✴ By convention, they have a type attribute that indicates what kind of action we are performing ✴ Flux standard actions also have a payload attribute used to propagate the data needed to determine the next state
  • 53. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ACTIONS CREATORS ✴ Action creators are function that create actions ✴ They do not dispatch actions to the store, making them portable and easier to test as they have no side-effects
  • 54. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 HOW AN ACTION LOOKS LIKE { type: 'LOADED', payload: { text: 'Do something.' } }
  • 55. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ACTIONS SERVICES ✴ Encapsulating related-actions dispatchers in the same file promote code reuse and organization ✴ Making them @Injectable() allow to use them within the dependency injection system of Angular
  • 56. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ACTIONS SERVICES import { Injectable } from '@angular/core'; import { NgRedux } from 'ng2-redux'; import { UserProfileState } from '../model/profile-state'; @Injectable() export class ProfileLoadingActions { static LOADED: string = 'PROFILE_LOADED'; constructor(private ngRedux: NgRedux<UserProfileState>) {} loaded(): void { this.ngRedux.dispatch({ type: ProfileLoadingActions.LOADED }); } }
  • 57. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 REDUCERS AS PURE FUNCTIONS ✴ Reducers are pure functions that, given an input, return always the same output as a fresh representation of a slice of the app state ✴ When the store needs to know how an action changes the state, it asks the reducers
  • 58. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 HOW A REDUCER LOOKS LIKE import { ProfileLoadingActions } from '../../actions/loading-actions'; import { INITIAL_STATE } from './loading-initial-state'; export function isLoadingReducer(state: boolean = INITIAL_STATE, action: any): boolean { switch (action.type) { case ProfileLoadingActions.LOADED: return true; case ProfileLoadingActions.UPDATED: return true; case ProfileLoadingActions.UPDATE: return false; case ProfileLoadingActions.NOT_LOADED: return false; default: return state; } }
  • 59. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 DESIGNING THE STORE ✴ The store is not a specular copy of the application model ✴ The store represents the information needed to properly render the UI elements of a web application
  • 60. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 COMBINE REDUCERS ✴ The most common state shape for a Redux app is a POJO containing slices of the model at each top-level key ✴ combineReducers takes an object full of slice reducer functions, and returns a new reducer function ✴ It is a utility function to simplify the most common use case of combining multiple reducers to describe the app state
  • 61. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 COMBINING REDUCERS IN ACTION import { loadingReducer as isLoading } from './loading/loading.reducer.ts'; import { errorReducer as error } from './error/error.reducer.ts'; export const rootReducer = combineReducers({ isLoading, error, });
  • 63. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 COMBINE ACTIONS ✴ Combining actions together can be a nice way to simplify the application state ✴ Executing in sequence or in parallel multiple actions can help the semantic of your code ✴ There is a middleware (redux-combine-actions) to easy combine async actions and dispatch them
  • 64. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 INTEGRATION import { createStore, combineReducers, applyMiddleware } from 'redux'; import combineActionsMiddleware from 'redux-combine-actions'; let createStoreWithMiddleware = applyMiddleware(combineActionsMiddleware)(createStore); let rootReducer = combineReducers(reducers); // Store initialization let rootStore = createStoreWithMiddleware(rootReducer);
  • 65. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 USING REDUX-COMBINE-ACTIONS export function addTodo(text) { return { type: 'ADD_TODO', text }; } export function increment() { return { type: 'INCREMENT_COUNTER' }; } // Combine "addTodo" and "increment" actions export function addTodoAndIncrement({text}) { return { types: [ 'COMBINED_ACTION_START', 'COMBINED_ACTION_SUCCESS', 'COMBINED_ACTION_ERROR' ], // Pass actions in array payload: [addTodo.bind(null, text), increment] }; } // Dispatch action store.dispatch(addTodoAndIncrement({text:'Dispatch combined action'}));
  • 67. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ASYNCHRONOUS CODE ✴ Redux manages only synchronous actions ✴ The web is based on asynchronous calls
  • 69. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 MANAGING ASYNC CODE WITH REDUX import { Component, OnInit } from '@angular/core'; import { WebApiPromiseService } from './profile.service'; @Component({ selector: 'user-profile', templateUrl: './user-profile.component.html' }) export class UserProfileComponent implements OnInit { constructor(private myPromiseService: WebApiPromiseService private profileActions: ProfileLoadingActions) {} ngOnInit() { this.myPromiseService .getService('v1/user/profile') .then(result => this.profileActions.loaded()) .catch(error => console.log(error)); } }
  • 70. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 REDUX THUNK ✴ A thunk is a function that wraps an expression to delay its evaluation ✴ Redux-Thunk middleware allows to write action creators that return a function instead of an action ✴ The thunk can be used to delay the dispatch of an action, or to dispatch it only if a certain condition is met
  • 71. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 INTEGRATION import { createStore, combineReducers, applyMiddleware } from 'redux'; import combineActionsMiddleware from 'redux-combine-actions'; import thunk from 'redux-thunk'; let createStoreWithMiddleware = applyMiddleware(thunk) (combineActionsMiddleware)(createStore); let rootReducer = combineReducers(reducers); // Store initialization let rootStore = createStoreWithMiddleware(rootReducer);
  • 72. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 REDUX THUNK AND PROMISES function fetchSecretSauce(): Promise { return fetch('https://www.google.com/search?q=secret+sauce'); } function apologize(fromPerson: string, toPerson: string, error: any) { return { type: 'APOLOGIZE', fromPerson, toPerson, error }; } function makeSandwich(forPerson: string, secretSauce: string) { return { type: 'MAKE_SANDWICH', forPerson, secretSauce }; } function makeASandwichWithSecretSauce(forPerson: string) { return function (dispatch: any) { return fetchSecretSauce().then( sauce => dispatch(makeSandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) ); }; } // In your action service store.dispatch(makeASandwichWithSecretSauce('Me'));
  • 73. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 WHAT’S HAPPENED ✴ A function that accepts dispatch as argument get returned so that dispatch can be called later ✴ The thunk middleware transformed thunk async actions into actions
  • 75. @giorgionatili@DroidconBos THE SELECT PATTERN ✴ The select pattern allows you to get slices of your state as RxJS observables ✴ The @select decorator can be added to the property of any class or Angular component/injectable ✴ It will turn the property into an observable which observes the Redux Store value which is selected by the decorator's parameter
  • 76. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 SELECTORS // Selects `counter` from the store and attaches it to this property @select() counter; // Selects `counter` from the store and attaches it to this property @select('counter') counterSelectedWithString; // Selects `pathDemo.foo.bar` from the store and attaches it the property @select(['pathDemo', 'foo', 'bar']) pathSelection; // This selects `counter` from the store and attaches it to this property @select(state => state.counter) counterSelectedWithFunction; // Selects `counter` from the store and multiples it by two @select(state => state.counter * 2) counterSelectedWithFuntionAndMultipliedByTwo: Observable<any>;
  • 77. @giorgionatili@DroidconBos REUSABLE SELECTORS ✴ A reusable selector is a function that return the selector ✴ By using reusable selectors it’s possible to minimize the refactoring effort
  • 78. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 FRACTAL STORES ✴ A subStore expose the same interface as the main Redux store (dispatch, select, etc.) ✴ A subStore is rooted at a particular path in your global state ✴ A subStore is used in components that have instance- specific access to Redux features
  • 79. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 IMPLEMENTATION export const userComponentReducer = (state, action) => action.type === 'ADD_LOCATION' ? { ...state, location: state.location + action.payload } : state; export class UserComponent implements NgOnInit { name: Observable<string>; occupation: Observable<string>; location: Observable<string>; private subStore: ObservableStore<User>; constructor(private ngRedux: NgRedux<UserProfileState>) {} onInit() { this.subStore = this.ngRedux.configureSubStore( ['users', userId], userComponentReducer); // Substore selectors are scoped to the base path used to configure the substore this.name = this.subStore.select('name'); this.occupation = this.subStore.select('occupation'); this.location = this.subStore.select(s => s.location || 0); }
  • 81. PREDICTABLE STATES In an hybrid world
  • 82. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 QUICK RECAP ✴ AngularJS and Angular can coexist in the same code base ✴ It’s possible to use ui-router and the Angular routing system together ✴ Redux is a tool to support architectures with a centralized, independent and decouple state
  • 83. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 APPLICATION LAYER Root app Bootstrap AngularJS and Angular Initialize the store and the initial state Angular route Actions and creators Define the DSL of the application Decouple the creation of the actions as injectable services Manipulate the application stateReducers Propagate the state of the application as an immutable objectReducers
  • 84. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 COMPONENTS ✴ Responsible of defining a node in your application three ✴ Responsible of fulfilling a single use case of the application ✴ Containing container components and legacy components ✴ Connecting legacy and container components with
  • 85. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 CONTAINER COMPONENTS ✴ May contain both presentational and container components ✴ Markup is minimal and mostly contains presentation components ✴ Are stateless because represent the datasource of presentation components ✴ Use actions to communicate the intent of changing the state ✴ Listen to slices of the state updates with selectors
  • 86. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 PRESENTATION COMPONENTS ✴ Have no dependencies on the rest of the app (i.e. actions) ✴ They don’t specify how the data is loaded or mutated ✴ Receive data and callbacks exclusively via @Input() ✴ Are written as functional components with no lifecycle hooks ✴ Communicate with parents through @Output()
  • 87. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 STATE CHANGE
  • 88. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 FIVE THINGS YOU SHOULD DO WHEN USING REDUX ✴ Design the application state before starting to code ✴ Build independent and self-contained modules using fractal stores ✴ Implement action creators to don't repeat yourself ✴ Use filters and selectors to listen to changes of specific slices ✴ Remove as much as possible the logic from the components
  • 89. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 FIVE THINGS YOU SHOULDN’T DO WHEN USING REDUX ✴ Store in the application state redundant information ✴ Pollute and make the DSL ambiguous with not needed actions ✴ Use Redux as an event bus system ✴ Apply optimistic changes when interacting with external API ✴ Create complex reducers instead of splitting the state
  • 90. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 PERFORMANCES HINT: RESELECT ✴ Selectors can compute derived data, allowing Redux to store the minimal possible state ✴ A selector is not recomputed unless one of its arguments change ✴ Selectors are composable can be used as input to other selectors
  • 92. ““The cleaner and nicer the program, the faster it's going to run. And if it doesn't, it'll be easy to make it fast.” - Joshua Bloch