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

undo redo with angular ngrx scenelab io
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

UNDO-REDO WITH ANGULAR & NGRX

slide-2
SLIDE 2

2

scenelab.io

@n_mehlhorn

slide-3
SLIDE 3

Minimize opportunity for error, but accept that mistakes will happen

3 @n_mehlhorn

slide-4
SLIDE 4

4

scenelab.io

@n_mehlhorn

slide-5
SLIDE 5

NILS MEHLHORN

freelance software engineer founder of scenelab.io

5

nils-mehlhorn.de

www

@n_mehlhorn

@n_mehlhorn

slide-6
SLIDE 6

6

NGRX BOOK

Pay what you want for the complete learning resource gum.co/angular-ngrx-book

@n_mehlhorn

slide-7
SLIDE 7

ERROR TOLERANCE = USER-FRIENDLY DESIGN

  • users have different backgrounds
  • ease onboarding

➙ confidence & creativity

7

browser supports only some interactions

@n_mehlhorn

slide-8
SLIDE 8

KEYBOARD SHORTCUTS: CONSIDERATIONS

8 @n_mehlhorn

  • common combinations: Ctrl + Z / Ctrl + Shift + Z
  • provide legend and/or tooltips
  • consider existing browser shortcuts
  • consider internationalization
slide-9
SLIDE 9

KEYBOARD SHORTCUTS: IMPLEMENTATION

9 @n_mehlhorn

  • Key Event Bindings
  • EventManager
  • NgRx Effect

<div (keydown.control.z)="undo()" (keydown.control.shift.z)="redo()”> </div>

Recommended Read

Keyboard Shortcuts in Angular -- Netanel Basal

shortcut$ = createEffect(() => fromEvent(document, ‘keydown’) .pipe(...) )

slide-10
SLIDE 10

10

component service service service service service component component

State everywhere Where to begin?

@n_mehlhorn

slide-11
SLIDE 11

REDUX / NGRX ARCHITECTURE

11

STORE REDUCER UI

ACTION

dispatch trigger update render

slide-12
SLIDE 12

12

store component service component service service component service

Single Source of Truth

@n_mehlhorn

slide-13
SLIDE 13

READ-ONLY STATES

13

S1 S3 S2 REDUCER S4 Services Components Undo-Redo History?

ACTION

@n_mehlhorn

dispatch

slide-14
SLIDE 14

PURE FUNCTIONS

14

REDUCER S2

ACTION

@n_mehlhorn

S1 META-REDUCER Put Undo-Redo Logic here

slide-15
SLIDE 15

HISTORY OF STATES

15 interface History { 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 default: const newPresent = reducer(state, action) history = { past: [history.present, ...history.past], present: newPresent, future: [] // clear future } return newPresent @n_mehlhorn

slide-16
SLIDE 16

HISTORY OF STATES

👎 intuitive implementation 👎 most libraries do this 👏 it can get big 👏 it’s all or nothing

16 @n_mehlhorn

slide-17
SLIDE 17

17

A1 A2 S1 S2 S3

undo

undoable not undoable

ALL OR NOTHING: GOING BACK MEANS LOSING THE GREEN SQUARE

@n_mehlhorn

slide-18
SLIDE 18

HISTORY OF STATES

👎 intuitive implementation 👎 most libraries do this 👏 it can get big 👏 it’s all or nothing

18

careful reducer composition required

@n_mehlhorn

slide-19
SLIDE 19

19

S1 A1 S2 A2 S3 A3 S4 not undoable S1 A1 S2 A2 S3

undo

not undoable S1 A2 S3a

undo

not undoable

HISTORY OF ACTIONS

@n_mehlhorn interface History { actions: Array<Action> base: State }

slide-20
SLIDE 20

HISTORY OF ACTIONS

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

slide-21
SLIDE 21

HISTORY OF ACTIONS

👎 actions < states 👎 ignore some actions 👏 tricky implementation 👏 expensive recalculation

21 @n_mehlhorn

slide-22
SLIDE 22

22

There’s another way back to the future

Delorean Vectors by Vecteezy

@n_mehlhorn

slide-23
SLIDE 23

23

JSON Patch

// 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" } @n_mehlhorn

slide-24
SLIDE 24

24

Inverse JSON Patch

// 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" } @n_mehlhorn

slide-25
SLIDE 25

25

COPY-ON-WRITE

state next draft

@n_mehlhorn

slide-26
SLIDE 26

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

slide-27
SLIDE 27

HISTORY OF PATCHES

👎 lightweight 👎 ignore some actions 👎 feasible implementation 👎 no recalculation 👊 requires Immer

27 @n_mehlhorn

slide-28
SLIDE 28

NGRX-WIEDER

28

  • patch-based undo-redo
  • ignore actions
  • merge actions
  • segmentation

DEMO

@n_mehlhorn

slide-29
SLIDE 29

Place your screenshot here

29

1. Visit Blog 2. Join Mailing List 3. Follow On Twitter 4. Work With Me

nils-mehlhorn.de

www

@n_mehlhorn