如何将自定义输入指令及其父表单重置为 $pristine

How to reset custom input directive and its parent form to $pristine

我已经实现了自定义输入指令 - counter 具有重置功能。该指令有 require: "ngModel".

我正在用 $setPristine() 重置指令 ngModel 的原始状态。与 $setDirty() 不同,$setPristine() 不会触及父窗体的 $pristine 状态。

问:如何"notify"父窗体不再有这个指令"dirty",这样父窗体就可以有它的$pristine 状态重置?

请记住,仅调用 form.$setPristine() 是不够的,因为表单中可能还有其他 "dirty" 控件,我的指令不会(也不应该)知道这些控件。

这是指令的 link 函数:

link: function(scope, element, attrs, ngModel){

  var original;

  ngModel.$render = function(){
    original = scope.counter = ngModel.$viewValue;
  };

  scope.up = function(){
    ngModel.$setViewValue(++scope.counter);
  };

  scope.reset = function(){
    scope.counter = original;
    ngModel.$setViewValue(scope.counter);
    ngModel.$setPristine(); // this sets $pristine on the directive, but not the form
  };
}

使用方法如下:

<div ng-form="form">
  <counter ng-model="count"></counter>
</div>

plunker

这是一个不太好的解决方案。遍历附加到表单的控件并检查是否还有脏控件。

我使用解释方法 here 来获取控件。

Plunker

app.directive("counter", function(){
  return {
    require: "ngModel",
    template: '<button ng-click="up()">{{counter}}</button><button ng-click="reset()">reset</button>',
    link: function(scope, element, attrs, ngModel){

      var original;

      ngModel.$render = function(){
        original = scope.counter = ngModel.$modelValue;
      };

      scope.up = function(){
        ngModel.$setViewValue(++scope.counter);
      };

      scope.reset = function(){
        scope.counter = original;
        ngModel.$setViewValue(scope.counter);
        ngModel.$setPristine();

        // check if one of the controls attached to the form is still dirty
        var dirty = false;
        angular.forEach(scope.form, function(val, key) {
            if (key[0] != '$') {
              if (val.$dirty) {
                dirty = true; 
              }
            }
        });
        if(!dirty) scope.form.$setPristine();


      };
    }
  };
});   

从 Angular1.3.x 开始,没有内置解决方案。

form.$setPristine() 在其所有子控件上设置 pristine。 (link to code)

ngModel.$setPristine() 仅在其自身上设置 $pristine (link to code)

解决此问题的一种方法是创建一个与 form 指令并存的指令,并劫持 form.$setDirty 以跟踪脏控件计数。这可能最好在 link 之前的阶段完成(即在子控件开始自行注册之前)。

app.directive("pristinableForm", function() {
  return {
    restrict: "A",
    require: ["pristinableForm", "form"],
    link: function(scope, element, attrs, ctrls) {
      var me = ctrls[0],
        form = ctrls[1];
      me.form = form;
      me.dirtyCounter = 0;
      var formSetDirtyFn = form.$setDirty;
      form.$setDirty = function() {
        me.dirtyCounter++;
        formSetDirtyFn();
      };
    },
    controller: function() {
      this.$notifyPristine = function() {
        if (this.dirtyCounter === 0) return;
        if (this.dirtyCounter === 1) {
          this.dirtyCounter = 0;
          if (this.form) this.form.$setPristine();
        } else {
          this.dirtyCounter--;
        }
      };
    }
  };
});

然后,自定义输入指令需要require: ["ngModel", "^pristinableForm"]并在其重置函数中调用pristinableForm.$notifyPristine()

scope.reset = function(){
  if (ngModel.$dirty){
    scope.counter = original;
    ngModel.$setViewValue(scope.counter);
    ngModel.$setPristine();
    pristinableForm.$notifyPristine();
  }
};

用法是:

<div ng-form="form" pristinable-form>
  <counter ng-model="count1"></counter>
  <counter ng-model="count2"></counter>
  <input ng-model="foo" name="anotherControl">
</div>

plunker