Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

undo redo functionality in Angular app

I am thinking of ways to implement undo,redo functionality in an angular app.

The first and most basic idea was to use a model that completely describes the app internals, lets call it AppModel. On every change worth recording, you simply create a new object of AppModel and push it on to the stack and update currentIndex.

In absolute worst case scenario, an object of AppModel would have to fill out 500 text fields, with average length of 20 characters. Thats 10000 characters or 10kB for every update.

How bad is this number? I don't think if would cause memory issues, but would it make the app freeze every time a push onto stack happens? This a basic implementation:

  historyStack: AppModel[];
  currentIndex:number = -1;

  push(newAppModel:AppModel){
    //delete all after current index
    this.historyStack.splice(++this.currentIndex, 0, newAppModel);
  }

  forward(){
    if(this.currentIndex < this.historyStack.length-1){
      this.currentIndex++;
      return this.historyStack[this.currentIndex];
    }
    return this.historyStack[this.currentIndex];
  }
  back(){
    return this.historyStack[this.currentIndex--];
  }

The other option I could think of, is to store function calls that perform the redo and reverse operations. This approach would require me to also store which objects need to call the functions. Those objects might me deleted by user, so there also must be a way to recreate the objects. This is getting painful as I type :)

What way do you recommend?

like image 802
sanjihan Avatar asked Mar 30 '18 08:03

sanjihan


People also ask

How do I undo a redo action?

To undo an action press Ctrl+Z. If you prefer your mouse, click Undo on the Quick Access Toolbar. You can press Undo (or CTRL+Z) repeatedly if you want to undo multiple steps. Note: For more information about the Quick Access Toolbar, see Customize the Quick Access Toolbar.

What are the features of undo and redo?

To undo an action, press Ctrl + Z. To redo an undone action, press Ctrl + Y. The Undo and Redo features let you remove or repeat single or multiple typing actions, but all actions must be undone or redone in the order you did or undid them – you can't skip actions.

How would you implement undo/redo feature in an application?

If “UNDO” string is encountered, pop the top element from Undo stack and push it to Redo stack. If “REDO” string is encountered, pop the top element of Redo stack and push it into the Undo stack. If “READ” string is encountered, print all the elements of the Undo stack in reverse order.

What is multi level undo redo?

A multi-level undo system increases the number of activities that may be reversed. Each important action is recorded internally and the user may invoke the undo function several times to cycle backwards through previous states. Multi-level undo is often paired with a redo command.


1 Answers

That's why it's recommended to have the state not in one single object, but to work with (business) modules where each module has a reasonable amount of properties.

I would recommend using a Redux framework like NGRX or NGXS for state management. For NGRX, there is a meta-reducer-library https://www.npmjs.com/package/ngrx-wieder which can wrap your NGRX reducers like this:

const reducer = (state, action: Actions, listener?: PatchListener) =>
  produce(state, next => {
    switch (action.type) {
      case addTodo.type:
        next.todos.push({id: id(), text: action.text, checked: false})
        return
      case toggleTodo.type:
        const todo = next.todos.find(t => t.id === action.id)
        todo.checked = !todo.checked
        return
      case removeTodo.type:
        next.todos.splice(next.todos.findIndex(t => t.id === action.id), 1)
        return
      case changeMood.type:
        next.mood = action.mood
        return
      default:
        return
    }
}, listener)

const undoableReducer = undoRedo({
  track: true,
  mergeActionTypes: [
    changeMood.type
  ]
})(reducer)

export function appReducer(state = App.initial, action: Actions) {
  return undoableReducer(state, action)
}

This way you don't have to write the undo/redo logic for each module's reducer over and over again, just wrap it in the meta reducer instead. And you can exclude the heavy part of the state that does not need to be undone. You can find a full Stackblitz example, as well as the basic part of the implementation code (utilizing patching with ImmerJS) here: https://nils-mehlhorn.de/posts/angular-undo-redo-ngrx-redux

like image 114
Phil Avatar answered Oct 11 '22 16:10

Phil