Угловая форма автоугонных углов - это правильный путь?

Моя цель - автоматическое сохранение формы после того, как она действительна, и обновить ее с помощью тайм-аута. Я настраивал как:

(function(window, angular, undefined) {
    'use strict';
    angular.module('nodblog.api.article', ['restangular'])
        .config(function (RestangularProvider) {
            RestangularProvider.setBaseUrl('/api');
            RestangularProvider.setRestangularFields({
                id: "_id"
            });
            RestangularProvider.setRequestInterceptor(function(elem, operation, what) {
                if (operation === 'put') {
                    elem._id = undefined;
                    return elem;
                }
                return elem;
            }); 
        })
        .provider('Article', function() {
            this.$get = function(Restangular) {
                function ngArticle() {};
                ngArticle.prototype.articles = Restangular.all('articles');
                ngArticle.prototype.one = function(id) {
                    return Restangular.one('articles', id).get();
                };
                ngArticle.prototype.all = function() {
                    return this.articles.getList();
                };
                ngArticle.prototype.store = function(data) {
                    return this.articles.post(data);
                };
                ngArticle.prototype.copy = function(original) {
                    return  Restangular.copy(original);
                };
                return new ngArticle;
            }
    })
})(window, angular);

angular.module('nodblog',['nodblog.route'])
.directive("autosaveForm", function($timeout,Article) {
    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            var id = null;
            scope.$watch('form.$valid', function(validity) {
                if(validity){
                    Article.store(scope.article).then(
                        function(data) {
                            scope.article = Article.copy(data);
                            _autosave();
                        }, 
                        function error(reason) {
                            throw new Error(reason);
                        }
                    );
                }  
            })
            function _autosave(){
                    scope.article.put().then(
                    function() {
                        $timeout(_autosave, 5000); 
                    },
                    function error(reason) {
                        throw new Error(reason);
                    }
                );
            }
        }
    }
})

.controller('CreateCtrl', function ($scope,$location,Article) {
        $scope.article = {};
        $scope.save = function(){
            if(typeof $scope.article.put === 'function'){
                $scope.article.put().then(function() {
                    return $location.path('/blog');
                });
            }
            else{
                Article.store($scope.article).then(
                    function(data) {
                        return $location.path('/blog');
                    }, 
                    function error(reason) {
                        throw new Error(reason);
                    }
                );
            }
        };
     })

Мне интересно, есть ли лучший способ.

Ответ 1

Глядя на код, я вижу, что $watch не будет повторно запущен, если текущий вход действителен, и пользователь также изменит все, что действительно. Это связано с тем, что функции часов выполняются только в том случае, если значение изменилось. Вы также должны проверить грязное состояние формы и reset, когда данные формы сохраняются, иначе вы получите бесконечный цикл сохранения.

И вы не очищаете никаких предыдущих тайм-аутов.

И текущий код сохранит недопустимые данные, если текущий таймаут выполняется.

Я запустил директиву, которая делает все это и лучше SOC, чтобы ее можно было повторно использовать. Просто представьте это выражение обратного вызова, и вы хорошо пойдете.

Посмотрите в действии в этом плунтере.

Демо-контроллер

myApp.controller('MyController', function($scope) {

  $scope.form = {
    state: {},
    data: {}
  };

  $scope.saveForm = function() {
    console.log('Saving form data ...', $scope.form.data);  
  };

});

Demo Html

  <div ng-controller="MyController">

    <form name="form.state" auto-save-form="saveForm()">

      <div>
        <label>Numbers only</label>
        <input name="text" 
               ng-model="form.data.text" 
               ng-pattern="/^\d+$/"/>
      </div>

      <span ng-if="form.state.$dirty && form.state.$valid">Updating ...</span>      

    </form>
  </div>

Директива

myApp.directive('autoSaveForm', function($timeout) {

  return {
    require: ['^form'],
    link: function($scope, $element, $attrs, $ctrls) {

      var $formCtrl = $ctrls[0];
      var savePromise = null;
      var expression = $attrs.autoSaveForm || 'true';

      $scope.$watch(function() {

        if($formCtrl.$valid && $formCtrl.$dirty) {

          if(savePromise) {
            $timeout.cancel(savePromise);
          }

          savePromise = $timeout(function() {

            savePromise = null;

            // Still valid?

            if($formCtrl.$valid) {

              if($scope.$eval(expression) !== false) {
                console.log('Form data persisted -- setting prestine flag');
                $formCtrl.$setPristine();  
              }

            }

          }, 500);
        }

      });
    }
  };

});

Ответ 2

UPDATE: остановить таймаут вся логика в директиве

.directive("autosaveForm", function($timeout,$location,Post) {
    var promise;
    return {
        restrict: "A",
        controller:function($scope){
            $scope.post = {};
            $scope.save = function(){
                console.log(promise);
                $timeout.cancel(promise);
                if(typeof $scope.post.put === 'function'){
                    $scope.post.put().then(function() {
                        return $location.path('/post');
                    });
                }
                else{
                    Post.store($scope.post).then(
                        function(data) {
                            return $location.path('/post');
                        }, 
                        function error(reason) {
                            throw new Error(reason);
                        }
                    );
                }
            };

        },
        link: function (scope, element, attrs) {
            scope.$watch('form.$valid', function(validity) {
                element.find('#status').removeClass('btn-success');
                element.find('#status').addClass('btn-danger');
                if(validity){
                    Post.store(scope.post).then(
                        function(data) {
                            element.find('#status').removeClass('btn-danger');
                            element.find('#status').addClass('btn-success');
                            scope.post = Post.copy(data);
                            _autosave();
                        }, 
                        function error(reason) {
                            throw new Error(reason);
                        }
                    );
                }  
            })
            function _autosave(){
                    scope.post.put().then(
                    function() {
                        promise = $timeout(_autosave, 2000);
                    },
                    function error(reason) {
                        throw new Error(reason);
                    }
                );
            }
        }
    }
})

Ответ 3

Вот вариант директивы Null, созданный, потому что я начал видеть ошибки "Бесконечные $digest Loop". (Я подозреваю, что что-то изменилось в Angular, когда отмена/создание $timeout() теперь вызывает дайджест.)

В этом варианте используется правильное выражение $watch - наблюдение за грязной и действительной формой, а затем вызывает $setPristine() раньше, поэтому часы будут перезапускаться, если форма снова перейдет в грязную. Затем мы используем $interval, чтобы дождаться паузы в этих грязных уведомлениях перед сохранением формы.

app.directive('autoSaveForm', function ($log, $interval) {

  return {
    require: ['^form'],
    link: function (scope, element, attrs, controllers) {

      var $formCtrl = controllers[0];
      var autoSaveExpression = attrs.autoSaveForm;
      if (!autoSaveExpression) {
        $log.error('autoSaveForm missing parameter');
      }

      var savePromise = null;
      var formModified;

      scope.$on('$destroy', function () {
        $interval.cancel(savePromise);
      });

      scope.$watch(function () {
        // note: formCtrl.$valid is undefined when this first runs, so we use !$formCtrl.$invalid instead
        return !$formCtrl.$invalid && $formCtrl.$dirty;
      }, function (newValue, oldVaue, scope) {

        if (!newValue) {
          // ignore, it not "valid and dirty"
          return;
        }

        // Mark pristine here - so we get notified again if the form is further changed, which would make it dirty again
        $formCtrl.$setPristine();

        if (savePromise) {
          // yikes, note we've had more activity - which we interpret as ongoing changes to the form.
          formModified = true;
          return;
        }

        // initialize - for the new interval timer we're about to create, we haven't yet re-dirtied the form
        formModified = false;

        savePromise = $interval(function () {

          if (formModified) {
            // darn - we've got to wait another period for things to quiet down before we can save
            formModified = false;
            return;
          }

          $interval.cancel(savePromise);
          savePromise = null;

          // Still valid?

          if ($formCtrl.$valid) {

            $formCtrl.$saving = true;
            $log.info('Form data persisting');

            var autoSavePromise = scope.$eval(autoSaveExpression);
            if (!autoSavePromise || !autoSavePromise.finally) {
              $log.error('autoSaveForm not returning a promise');
            }

            autoSavePromise
            .finally(function () {
              $log.info('Form data persisted');
              $formCtrl.$saving = undefined;
            });
          }
        }, 500);

      });
    }
  };

});