Upgrade to Pro — share decks privately, control downloads, hide ads and more …

STORE Trek: The Voyage to Angular (ngVikings 2019)

STORE Trek: The Voyage to Angular (ngVikings 2019)

You know that the journey from AngularJS to Angular can be tricky. You may also know that NgRx can help make your Angular applications be more scalable and testable. But did you know that NgRx and ngUpgrade can work together to take your upgrade to warp factor 10 ?

In this talk, Mike Ryan and Sam Julien — the NgRx guy and the ngUpgrade guy — will teach you how to inject your NgRx store into your AngularJS code. You can then dispatch actions and trigger effects from AngularJS! You’ll learn how to set up this strategy and how to use it to migrate step-by-step. You’ll also learn how NgRx + ngUpgrade can give you greater flexibility in your upgrade by decoupling your business logic, how you can call AngularJS services with effects (even when they’re returning promises), and how to gracefully transition a feature from AngularJS all the way to Angular with NgRx.

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

May 27, 2019
Tweet

More Decks by Sam Julien

Other Decks in Technology

Transcript

  1. STORE TREK The Voyage to Angular MIKE RYAN SAM JULIEN

    STORE TREK THE VOYAGE TO ANGULAR
  2. None
  3. None
  4. None
  5. None
  6. STATE Warp speed is set to a number between 3

    and 9.95 The weapons are “firing” or “online” The shields are “raised” or “lowered”
  7. SIDE EFFECTS When the user presses “Fire Torpedoes” the application

    needs to reach out to the Weapons API
  8. dashboardService weaponsService <Status/> <Weapons/> <Warp/> <Shields/> DASHBOARD $rootScope

  9. $rootScope

  10. None
  11. ARCHITECTURAL LIMITATIONS

  12. $ROOTSCOPE EVERYWHERE!

  13. export class DashboardService { fireTorpedoes() { this.$rootScope.$broadcast( this.FIRE_TORPEDOES, "Firing torpedoes!"

    ); } onFireTorpedoes($scope, handler) { $scope.$on(this.FIRE_TORPEDOES, (event, message) => { handler(message); }); } }
  14. export class DashboardService { fireTorpedoes() { this.$rootScope.$broadcast( this.FIRE_TORPEDOES, "Firing torpedoes!"

    ); } onFireTorpedoes($scope, handler) { $scope.$on(this.FIRE_TORPEDOES, (event, message) => { handler(message); }); } }
  15. export class DashboardService { fireTorpedoes() { this.$rootScope.$broadcast( this.FIRE_TORPEDOES, "Firing torpedoes!"

    ); } onFireTorpedoes($scope, handler) { $scope.$on(this.FIRE_TORPEDOES, (event, message) => { handler(message); }); } }
  16. THE BUSINESS LOGIC IS TIGHTLY COUPLED TO THE VIEW!

  17. weaponsController.$inject = ["dashboardService"]; function weaponsController(dashboardService) { const vm = this;

    vm.fireWeapons = () => { dashboardService.fireTorpedoes(); }; }
  18. weaponsController.$inject = ["dashboardService"]; function weaponsController(dashboardService) { const vm = this;

    vm.fireWeapons = () => { dashboardService.fireTorpedoes(); }; }
  19. WHAT IF… The warp drive algorithm changes? The weapons system

    changes? We need to use this system for multiple ships?
  20. HOW CAN WE SUCCESSFULLY MIGRATE THIS APP TO ANGULAR?

  21. SOFTWARE ENGINEER AT SYNAPSE GOOGLE DEVELOPER EXPERT NGRX CORE TEAM

    MIKE RYAN @mikeryandev
  22. SAM JULIEN @samjulien CONTENT ENGINEER AT AUTH0 GDE & ANGULAR

    COLLABORATOR UPGRADINGANGULARJS.COM
  23. THE SOLUTION: NGRX AS A MEDIATOR BETWEEN FRAMEWORKS

  24. Store (Business Logic) View Layer Component Component Component CHANGES DATA

  25. Store (Business Logic) View Layer AngularJS Angular ??? CHANGES DATA

  26. YOU MIGHT NOT NEED NGRX!

  27. APPLICATION STATE THAT CHANGES FREQUENTLY APPLICATIONS WITH MANY SOURCES OF

    CHANGE LARGE-SCALE ENTERPRISE APPLICATIONS YOU MAY NEED NGRX IF…
  28. NGRX IS NOT MADE FOR CRUD APPLICATIONS

  29. NGRX ISN’T ALL OR NOTHING!

  30. WHAT’S THE FUNDAMENTAL POWER BEHIND USING NGRX?

  31. INDIRECTION

  32. None
  33. Service Component

  34. Service Component Dispatcher

  35. Torpedoes Service AngularJS Component Dispatcher

  36. Torpedoes Service Angular Component Dispatcher

  37. Phasers Service Angular Component Dispatcher

  38. WAITER VS. CHEF

  39. ' )

  40. class ShieldsComponent { @Input() status: "up" | "down"; @Output() raiseShields:

    EventEmitter; }
  41. class ShieldsComponent { @Input() status: "up" | "down"; @Output() raiseShields:

    EventEmitter; } DOES THIS COMPONENT KNOW WHO IS BINDING TO ITS INPUT?
  42. class ShieldsComponent { @Input() status: "up" | "down"; @Output() raiseShields:

    EventEmitter; } DOES THIS COMPONENT KNOW WHO IS LISTENING TO ITS OUTPUT?
  43. THERE IS INDIRECTION BETWEEN AN ANGULAR COMPONENT AND ITS PARENT

  44. HOW NGRX USES INDIRECTION

  45. Reducers Components Effects TRANSITION STATE CALL SERVICES HANDLE INTERACTION SINGLE

    RESPONSIBILITY PRINCIPLE
  46. Reducers Components Effects ACTIONS

  47. Reducers Components Effects SELECTORS

  48. ACTIONS

  49. interface Action { type: string; }

  50. const selectWarpSpeedAction: Action = { type: "[Warp] Select Warp Speed",

    speed: 9.95 };
  51. [Weapons] Fire Torpedoes [Shields] Raise Shields [Weapons API] Fire Torpedoes

    Success [Warp] Select Warp Speed
  52. Components Dispatcher ACTIONS

  53. Components Store ACTIONS

  54. store.dispatch({ type: "[Shields] Raise Shields" });

  55. Reducers Components Effects

  56. Reducers ACTIONS INITIAL STATE STATE

  57. Effects ACTIONS ACTIONS

  58. ACTIONS INTRODUCE INDIRECT COMMUNICATION BETWEEN EFFECTS, STATE, AND THE VIEW

  59. SELECTORS

  60. QUERY & TRANSFORM STATE IN THE STORE

  61. WINDOWS INTO SHARED VALUES

  62. Store selectShieldsStatus <Shields/> “up” | “down”

  63. store.select(selectShieldStatus);

  64. COMPONENTS USE SELECTORS TO INDIRECTLY READ STATE IN THE STORE

  65. Reducers Components Effects

  66. $rootScope.$on('raiseShields') store.select(selectShieldsStatus);

  67. @Input() status: "up" | "down"; store.select(selectShieldsStatus);

  68. $rootScope.$broadcast('raiseShields') this.store.dispatch({ type: ‘[Shields] Raise Shields’ });

  69. @Output() raiseShields: EventEmitter this.store.dispatch({ type: ‘[Shields] Raise Shields’ });

  70. Service Layer AngularJS Components Store

  71. Service Layer Angular Components Store

  72. STORE TREK The Voyage to Angular MIKE RYAN SAM JULIEN

    UPGRADING WITH NGRX & NGUPGRADE
  73. TYPICAL STRATEGIES

  74. COMPONENT-BASED

  75. </> </> </> </> </> </> </>

  76. </> </> </> </> </> </> </>

  77. ROUTE-BASED

  78. </> </> </> </> </> </> </>

  79. </> </> </> </> </> </> </>

  80. THE NGRX UPGRADE STRATEGY

  81. ARCHITECTURE BEFORE MIGRATION

  82. THINK THROUGH THE ACTIONS ONE

  83. export enum Types { RaiseShields = "[Shields] Raise Shields", LowerShields

    = "[Shields] Lower Shields" } export interface RaiseShieldsAction { type: Types.RaiseShields; } export interface LowerShieldsAction { type: Types.LowerShields; }
  84. DEFINE STATE TWO

  85. export interface State { shieldsRaised: boolean; warpSpeed: number; firingTorpedoes: boolean;

    } export const initialState: State = { shieldsRaised: false, warpSpeed: 3, firingTorpedoes: false };
  86. RETHINK THE ARCHITECTURE FIRST

  87. MOVE BUSINESS LOGIC TO REDUCERS AND EFFECTS THREE

  88. export function reducer( state = initialState, action: ShieldsActions.Union ): State

    { switch (action.type) { case ShieldsActions.Types.RaiseShields: { return { ...state, shieldsRaised: true }; } case ShieldsActions.Types.LowerShields: { return { ...state, shieldsRaised: false }; } } }
  89. export function reducer( state = initialState, action: ShieldsActions.Union ): State

    { switch (action.type) { case ShieldsActions.Types.RaiseShields: { return { ...state, shieldsRaised: true }; } case ShieldsActions.Types.LowerShields: { return { ...state, shieldsRaised: false }; } } }
  90. export function reducer( state = initialState, action: ShieldsActions.Union ): State

    { switch (action.type) { case ShieldsActions.Types.RaiseShields: { return { ...state, shieldsRaised: true }; } case ShieldsActions.Types.LowerShields: { return { ...state, shieldsRaised: false }; } } }
  91. @Effect() fireTorpedoes$ = this.actions$.pipe( ofType(WeaponsActions.Types.FireTorpedoes), concatMap(() => { /* Call

    the Weapons Service */ }) );
  92. @Effect() fireTorpedoes$ = this.actions$.pipe( ofType(WeaponsActions.Types.FireTorpedoes), concatMap(() => { /* Call

    the Weapons Service */ }) );
  93. DOWNGRADE THE STORE FOUR

  94. angular .module(MODULE_NAME, ["ngRoute"]) .factory("Store", downgradeInjectable(Store));

  95. angular .module(MODULE_NAME, ["ngRoute"]) .factory("Store", downgradeInjectable(Store));

  96. weaponsController.$inject = ["Store"]; function weaponsController(store: Store<any>) { const vm =

    this; vm.fireWeapons = () => store.dispatch(createFireTorpedoesAction()); }
  97. weaponsController.$inject = ["Store"]; function weaponsController(store: Store<any>) { const vm =

    this; vm.fireWeapons = () => store.dispatch(createFireTorpedoesAction()); }
  98. USE ANGULARJS SERVICES

  99. export class WeaponsService { fireTorpedoes(): Promise<true> { return new Promise(resolve

    => { // fancy weapons algorithm… }); } }
  100. export class WeaponsService { fireTorpedoes(): Promise<true> { return new Promise(resolve

    => { // fancy weapons algorithm… }); } }
  101. @Effect() fireTorpedoes$ = this.actions$.pipe( ofType(WeaponsActions.Types.FireTorpedoes), concatMap(async () => { await

    this.upgrade.$injector .get("weaponsService") .fireTorpedoes(); return WeaponsActions.createFireTorpedoesSuccessAction(); }) );
  102. @Effect() fireTorpedoes$ = this.actions$.pipe( ofType(WeaponsActions.Types.FireTorpedoes), concatMap(async () => { await

    this.upgrade.$injector .get("weaponsService") .fireTorpedoes(); return WeaponsActions.createFireTorpedoesSuccessAction(); }) );
  103. @Effect() fireTorpedoes$ = this.actions$.pipe( ofType(WeaponsActions.Types.FireTorpedoes), concatMap(async () => { await

    this.upgrade.$injector .get("weaponsService") .fireTorpedoes(); return WeaponsActions.createFireTorpedoesSuccessAction(); }) );
  104. @Effect() fireTorpedoes$ = this.actions$.pipe( ofType(WeaponsActions.Types.FireTorpedoes), concatMap(async () => { await

    this.upgrade.$injector .get("weaponsService") .fireTorpedoes(); return WeaponsActions.createFireTorpedoesSuccessAction(); }) );
  105. SUBSCRIBE TO STATE WITH SELECTORS FIVE

  106. statusController.$inject = ["Store"]; function statusController(store: Store<fromRoot.AppState>) { const torpedoesSubscription =

    store .select(fromRoot.selectShipIsFiringTorpedoes) .subscribe(isFiringTorpedoes => { vm.weaponsStatus = isFiringTorpedoes ? "Firing torpedoes!" : "Weapons online."; }); vm.$onDestroy = () => torpedoesSubscription.unsubscribe(); }
  107. statusController.$inject = ["Store"]; function statusController(store: Store<fromRoot.AppState>) { const torpedoesSubscription =

    store .select(fromRoot.selectShipIsFiringTorpedoes) .subscribe(isFiringTorpedoes => { vm.weaponsStatus = isFiringTorpedoes ? "Firing torpedoes!" : "Weapons online."; }); vm.$onDestroy = () => torpedoesSubscription.unsubscribe(); }
  108. statusController.$inject = ["Store"]; function statusController(store: Store<fromRoot.AppState>) { const torpedoesSubscription =

    store .select(fromRoot.selectShipIsFiringTorpedoes) .subscribe(isFiringTorpedoes => { vm.weaponsStatus = isFiringTorpedoes ? "Firing torpedoes!" : "Weapons online."; }); vm.$onDestroy = () => torpedoesSubscription.unsubscribe(); }
  109. UPGRADE AT WARP SPEED

  110. export const shieldsComponent = { template: ` <button ng-click="$ctrl.raiseShields()">Raise</button> <button

    ng-click="$ctrl.lowerShields()">Lower</button> `, controller: shieldsController }; shieldsController.$inject = ["Store"]; function shieldsController(store: Store<any>) { const vm = this; vm.raiseShields = () => store.dispatch(createRaiseShieldsAction()); vm.lowerShields = () => store.dispatch(createLowerShieldsAction()); }
  111. @Component({ template: ` <button (click)="raiseShields()">Raise</button> <button (click)="lowerShields()">Lower</button> ` }) export

    class ShieldComponent { constructor(private store: Store<any>) {} raiseShields = () => this.store.dispatch(createRaiseShieldsAction()); lowerShields = () => this.store.dispatch(createLowerShieldsAction()); }
  112. Reducers AngularJS Effects

  113. $ROOTSCOPE BE GONE!

  114. BUSINESS LOGIC IS IN THE STORE

  115. CREATE ACTIONS TO CAPTURE APP EVENTS WRITE REDUCERS TO MANAGE

    STATE TRANSITIONS BUILD EFFECTS TO COMMUNICATE WITH SERVICES REARCHITECT THE APPLICATION TO NGRX STEP ONE DOWNGRADE STORE USING ANGULAR UPGRADE SUBSCRIBE TO SELECTORS IN YOUR COMPONENTS
  116. PICK A VIEW LAYER UPGRADE STRATEGY STEP TWO COMPONENTS ROUTES

    </> </> </> </> </> </> </> </> </> </> </> </> </> </>
  117. NgRx.io

  118. UpgradingAngularJS.com

  119. samj.im/ngvikings

  120. THANK YOU samj.im/ngvikings