undo redo with angular ngrx scenelab io
play

UNDO-REDO WITH ANGULAR & NGRX scenelab.io @n_mehlhorn 2 - PowerPoint PPT Presentation

UNDO-REDO WITH ANGULAR & NGRX scenelab.io @n_mehlhorn 2 Minimize opportunity for error, but accept that mistakes will happen @n_mehlhorn 3 scenelab.io @n_mehlhorn 4 NILS MEHLHORN nils-mehlhorn.de www freelance software engineer


  1. UNDO-REDO WITH ANGULAR & NGRX

  2. scenelab.io @n_mehlhorn 2

  3. Minimize opportunity for error, but accept that mistakes will happen @n_mehlhorn 3

  4. scenelab.io @n_mehlhorn 4

  5. NILS MEHLHORN nils-mehlhorn.de www freelance software engineer @n_mehlhorn founder of scenelab.io @n_mehlhorn 5

  6. NGRX BOOK Pay what you want for the complete learning resource gum.co/angular-ngrx-book @n_mehlhorn 6

  7. ERROR TOLERANCE = USER-FRIENDLY DESIGN ● users have different backgrounds ● ease onboarding ➙ confidence & creativity browser supports only some interactions @n_mehlhorn 7

  8. KEYBOARD SHORTCUTS: CONSIDERATIONS ● common combinations: Ctrl + Z / Ctrl + Shift + Z ● provide legend and/or tooltips ● consider existing browser shortcuts ● consider internationalization @n_mehlhorn 8

  9. KEYBOARD SHORTCUTS: IMPLEMENTATION ● Key Event Bindings <div (keydown.control.z)="undo()" (keydown.control.shift.z)="redo()”> </div> ● EventManager ● NgRx Effect Recommended Read Keyboard Shortcuts in Angular -- Netanel Basal shortcut$ = createEffect(() => fromEvent(document, ‘keydown’) .pipe(...) ) @n_mehlhorn 9

  10. State everywhere component Where to begin? service service service component service component service @n_mehlhorn 10

  11. dispatch trigger ACTION REDUCER UI STORE render update REDUX / NGRX ARCHITECTURE 11

  12. Single Source of Truth store service service service component component service component @n_mehlhorn 12

  13. READ-ONLY STATES ACTION dispatch REDUCER S4 S3 S2 S1 Services Components Undo-Redo History? @n_mehlhorn 13

  14. PURE FUNCTIONS S1 ACTION META-REDUCER REDUCER Put Undo-Redo Logic here S2 @n_mehlhorn 14

  15. default: const newPresent = reducer(state, action) history = { past: [history.present, ...history.past], present: newPresent, future: [] // clear future } interface History { return newPresent past: Array<State> present: State future: Array<State> case 'UNDO': } const previous = history.past[0] const newPast = history.past.slice(1) history = { past: newPast, present: previous, future: [history.present, ...history.future] } return previous HISTORY OF STATES @n_mehlhorn 15

  16. HISTORY OF STATES intuitive implementation it can get big 👎 👏 most libraries do this it’s all or nothing 👎 👏 @n_mehlhorn 16

  17. S1 S2 S3 A1 A2 not undoable undoable undo ALL OR NOTHING: GOING BACK MEANS LOSING THE GREEN SQUARE @n_mehlhorn 17

  18. HISTORY OF STATES intuitive implementation it can get big 👎 👏 most libraries do this it’s all or nothing 👎 👏 careful reducer composition required @n_mehlhorn 18

  19. A1 A2 A3 S1 S2 S3 S4 not undoable undo A1 A2 S1 S2 S3 not undoable undo interface History { A2 actions: Array<Action> S1 S3a base: State not } undoable HISTORY OF ACTIONS @n_mehlhorn 19

  20. interface History { actions: Array<Action> base: State } const lastState = history.actions .slice(0, -1) // every action except the last one .reduce( (state, action) => reducer(state, action), history.base ) HISTORY OF ACTIONS @n_mehlhorn 20

  21. HISTORY OF ACTIONS actions < states tricky implementation 👎 👏 ignore some actions expensive recalculation 👎 👏 @n_mehlhorn 21

  22. There’s another way back to the future @n_mehlhorn 22 Delorean Vectors by Vecteezy

  23. // initial state const state = { "firstname": "John" } // JSON Patch representing what reducer did to change the state const patch = [ { "op": "add", "path": "/lastname", "value": "Doe" } ] // result state when applying patch to S1 const next = { "firstname": "John", "lastname": "Doe" } JSON Patch @n_mehlhorn 23

  24. // result state from before const next = { "firstname": "John", "lastname": "Doe" } // JSON Patch representing the reverse // of what reducer did to change the state const inversePatch = [ { "op": "remove", "path": "/lastname" } ] // resulting initial state when applying inversePatch to S2 const state = { "firstname": "John" } Inverse JSON Patch @n_mehlhorn 24

  25. state draft next COPY-ON-WRITE @n_mehlhorn 25

  26. import produce, {applyPatches} from "immer" const state = { "firstname": "John" } let undoPatches const next = produce( state, draft => { draft.lastname = "Doe" }, (patches, inversePatches) => { undoPatches = inversePatches } ) const patched = applyPatches(next, undoPatches) expect(patched).toEqual(state) PATCHES WITH IMMER @n_mehlhorn 26

  27. HISTORY OF PATCHES lightweight requires Immer 👎 👊 ignore some actions 👎 feasible implementation 👎 no recalculation 👎 @n_mehlhorn 27

  28. NGRX-WIEDER ● patch-based undo-redo ● ignore actions ● merge actions ● segmentation DEMO @n_mehlhorn 28

  29. 1. Visit Blog Place your screenshot here 2. Join Mailing List 3. Follow On Twitter 4. Work With Me nils-mehlhorn.de www @n_mehlhorn 29

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend