Angular NgRx action based Undo/Redo system with effects and multiple stores.
Ability to undo/redo your actions is required in more and more enterprise applications. People just expect this type of fault tolerance from any complex application. Implementing that kind of behavior is easier when your angular application is using NgRx store for storing application state. In this post we’ll implement undo/redo mechanism for CRUD application using NgRx actions with multiple stores and fake conncection to backend. Link to stackblitz with example from artcile: https://stackblitz.com/edit/github-s2fk9e?embed=1&file=src/app/app.component.ts
In case of developing small application we can always memorize the whole state on each action that can be undone and easy check the differences. But usually in enterprise application this approach is unacceptable. We don’t want to store those big states in browser memory. That’s why in this article we will focus on storing actions.
Undo/redo application used in this article has three modules: core, land and project. It allows you to add, remove and update land and project table. When using application it is possible to undo and redo actions performed within application. If project or land table is modified we lose ability to perform redo action.
To implement undo redo mechanism we will need three elements:
- Undo/Redo store to store all actions.
- Meta reducer to capture actions which we want to save to undo/redo store.
- Reverse action for all action that we want to persist.
Undo redo store is responsible for storing past and future action, store is located in core module in undoredo folder. Also is registered in app.module.ts. State class have past and future array. isInProgress flag is optional, it helps tracking when undo/redo action is finished and block and unblock undo redo buttons.
UndoRedo store has only 4 action. Undo and Redo those actions are responsible for starting undo/redo operation. Second pair of actions are UndoSuccess/RedoSuccess and those actions are changing state of undoRedo store (pushing action from past to future array or from future to past)
The main challenge in undo/redo mechanism is how to undo/redo update action. Update action have in payload only changed object but we don’t know how this object looks before updating. But thanks to reducer we can reduce all save action apart last one and have state before updating.
This file is in undoredo.selector.ts file
Meta reducer is responsible for saving action to past array. It works like a interceptor. All actions are reduced in meta reducer and pass to appropriate reducer. Of course we want to save only success action and that’s why meta reducer is good place where we can select which action we want to save.
We must save get action to have last state (because we are reducing all save action and result of that is last state but without initial get action we will have null) but for simplicity we can save them always on the zero index of past array. Additionally we can save only get action that are unique (so only one getProject action and only one getLand action)
Meta reducer is placed in core module in metaReducer folder, also is registered in app.module.ts
Implementation of Reverse action mechanism is easy because in most of the cases we will have those reverse actions out of the box. So reverse action for remove is add , reverse action for add is remove and reverse action for update is update ;p. To achieve reverse action we need to implement undo/redo effect per store. Those effects waiting for Undo/Redo action. And depending on the action in payload they are dispatching reverse action and UndoSuccess/RedoSuccess action. Special case is update where we must have last state to correctly update selected object (you can read about it in Undo redo store part ).
Example effect is in project module store folder in project.effects.ts file.
Sooo … let’s used it.
Now to use it we need to dispatched undo or redo action and past them present or future action in payload like that.
Those method are in navigation component.
In summary implementing undo/redo action based mechanism is not so difficult as it seems . Thanks to NgRx mechanism such as meta reducer or router store you can easy store all needed action and undo/redo them as much as you want.
Big thanks to whole my team for supporting me during implementation and special thanks to Piotr S. for helping me in code and Marc D. for creating that kind of bizarre requirement for our app.