Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Predictable Web Apps with Angular and Redux

265 views

Published on

presented at Web Unleashed 2017
More info at www.fitc.ca/webu

Giorgio Natili
Akamai Technologies
Overview

There are a lot of different frameworks to use to build an enterprise web app, among them Angular is one of the most popular. In the past year, Angular changed dramatically evolving from the version 1.x known as AngularJS to the next generation of Angular.

Despite the excitement for the new version of the framework, a lot of organizations are facing several challenges because of the nature of the upgrade that is not retro-compatible with the old version of Angular (aka AngularJS).

During this presentation, you’ll learn first how to integrate AngularJS component in an Angular to reuse as much as possible the existing codebase and then how to design and develop a predictable state using Redux.

Objective

Learn how to upgrade your code base from AngularJS to Angular and how to create uni-directional architectures able to manage a predictable application state.

Target Audience

Engineers and engineering managers

Assumed Audience Knowledge

AngularJS

Five Things Audience Members Will Learn

How to use Angular 4 with AngularJS
How to upgrade components and services from AngularJS to Angular 4
What is Redux and how to use it with Angular 4
How to design an app with a predictive state
How to implement a state machine with Redux and Angular

Published in: Internet
  • Be the first to comment

  • Be the first to like this

Predictable Web Apps with Angular and Redux

  1. 1. PREDICTABLE WEB APPS WITH ANGULAR AND REDUX @giorgionatili
  2. 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)
  3. 3. Droidcon Boston bitly.com/dcbos18
  4. 4. Droidcon Boston bitly.com/dcbos18
  5. 5. Mobile Tea
  6. 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. 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. 8. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 TOOLS WE’LL USE + + &
  9. 9. ANGULARJS & ANGULAR Using AngularJS within Angular applications
  10. 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
  11. 11. CONFIGURING THE APP
  12. 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. 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. 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. 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. 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
  17. 17. @giorgionatili@DroidconBos THAT’S IT…
  18. 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
  19. 19. @giorgionatili@DroidconBos ANY OPTION?
  20. 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)
  21. 21. @giorgionatili@DroidconBos INTEGRATE THE TWO CODE BASES
  22. 22. ANGULARJS AND ANGULAR Integrating AngularJS components into Angular
  23. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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'); } }
  34. 34. @giorgionatili@DroidconBos ALMOST THERE
  35. 35. UPGRADING COMPONENTS
  36. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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); } }
  46. 46. @giorgionatili@DroidconBos RELATIONSHIPS OVERVIEW NgModelWrapper
 Controller NgModelWrapperUpgr adeComponent YourComponent
 WrapperController YourNG4Directive
  47. 47. @giorgionatili@DroidconBos ALL SET!!!
  48. 48. REDUX IN A NUTSHELL Predictably and Composability
  49. 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. 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. 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. 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. 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. 54. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 HOW AN ACTION LOOKS LIKE { type: 'LOADED', payload: { text: 'Do something.' } }
  55. 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. 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. 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. 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. 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. 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. 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, });
  62. 62. MORE ON ACTIONS
  63. 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. 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. 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'}));
  66. 66. ASYNCHRONOUS CODE
  67. 67. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 ASYNCHRONOUS CODE ✴ Redux manages only synchronous actions ✴ The web is based on asynchronous calls
  68. 68. @giorgionatili@DroidconBos AND NOW WHAT?!?
  69. 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. 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. 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. 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. 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
  74. 74. DETECT STATE CHANGES
  75. 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. 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. 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. 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. 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); }
  80. 80. @giorgionatili@DroidconBos PREDICTABLE STATE CONTAINERS ARE SIMPLE
  81. 81. PREDICTABLE STATES In an hybrid world
  82. 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. 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. 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. 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. 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. 87. @giorgionatili | @theSwiftFest | @DroidconBos#webu17 STATE CHANGE
  88. 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. 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. 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
  91. 91. QUESTIONS and potentially answers :)
  92. 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
  93. 93. THANKS! @giorgionatili
 


×