JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.js"></script>
<form id="app" ng-controller="Ctrl" undo-support="">
    <input name="name" undoable="" ng-model="data.name" /><br/>
    <input name="value" undoable="" ng-model="data.value" /><br/>
    <button ng-click="log()">Log</button>
    <button ng-click="undo()" ng-disabled="!canUndo">Undo</button>
    <button ng-click="redo()" ng-disabled="!canRedo">Redo</button>
    
    <p>
        An external change will break the functionality;
        here <code>extChange()</code> should create and add a command.
        <br/>
        <button ng-click="extChange()">External change</button>
    </p>
</form>

JavaScript

var app = angular.module("app",[]);

function MyData() {
    this.name = null;
    this.value = null;
}

app.controller("Ctrl", function($scope) {
    angular.extend($scope, {
        data: new MyData(),
        log: log,
        extChange: extChange
    });
    
    function log() {
        console.log($scope.data);
    }
    
    function extChange() {
        $scope.data.name = "xxx";
    }
});

function NgModelCommand(oldval, newval, applier) {
    this.oldval = oldval;
    this.newval = newval;
    this.applier = applier;
}
NgModelCommand.prototype.execute = function() {
    this.applier(this.newval);
};
NgModelCommand.prototype.rollback = function() {
    this.applier(this.oldval);
};

app.directive("undoSupport", function() {
    return {
        controller: function($scope) {
            var commands = [], i = -1;
            
            this.add = function(oldval, newval, applier) {
                i += 1;
                commands[i] = new NgModelCommand(oldval, newval, applier);
                if( i < commands.length-1 ) commands.length = i+1;
            };
            
            $scope.undo = function() {
                if( $scope.canUndo ) {
                    var cmd = commands[i];
                    i -= 1;
                    cmd.rollback();
                }
            };
            $scope.redo = function() {
                if( $scope.canRedo ) {
                    i += 1;
                    var cmd = commands[i];
                    cmd.execute();
                }
            };
            $scope.canUndo = false;
            $scope.canRedo = false;
            
            $scope.$watch(
                function() {
                    return commands.length > 0 && i >= 0;
                },
                function(newval) {
                    $scope.canUndo = newval;
                }
            );
            $scope.$watch(
                function() {
                    return commands.length > 0 && i < commands.length-1;
  ...