angularjs自动保存形式是正确的方式吗?

时间:2014-01-15 10:45:46

标签: angularjs angularjs-directive restangular

我的目标是在有效后自动保存表单并使用超时更新它。 我设置如下:

(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);
                    }
                );
            }
        };
     })

我想知道是否有最好的方式。

3 个答案:

答案 0 :(得分:28)

查看我能看到的代码是,如果当前输入有效并且用户也更改了任何有效的内容,则不会重新触发$ watch。这是因为只有在值发生变化时才会执行监视功能。 您还应检查表单的脏状态,并在表单数据保持不变时重置它,否则您将获得无限的持久循环。

你没有清除以前的超时。

如果当前超时正在进行,当前代码将保存无效数据。

我已经完成了一项指令,它完成了所有操作并且具有更好的SOC,因此可以重复使用。只需提供一个回调表达式就可以了。

See it in action in this plunker.

演示控制器

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

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

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

});

演示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);
        }

      });
    }
  };

});

答案 1 :(得分:2)

更新: 停止超时 指令中的所有逻辑

.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);
                    }
                );
            }
        }
    }
})

答案 2 :(得分:1)

这是Null指令的变体,因为我开始看到“Infinite $ 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's 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);

      });
    }
  };

});