ng-class $compile 之后的奇怪行为

ng-class strange behaviour after after $compile

我制定了以下指令:

(function () {
    'use strict';

    angular
        .module('app.widgets')
        .directive('zzForminput', formInput);

    function formInput($compile) {
        // Usage:
        //     <div zz-forminput></div>

        function setupDom(element) {
            var input = element.querySelector("input, textarea, select");
            var type = input.getAttribute("type");
            var name = input.getAttribute("name");
            if (type !== "checkbox" && type !== "radio") {
                input.classList.add("form-control");
            }
            var label = element.querySelector("label");
            label.classList.add("control-label");

            element.classList.add("form-group");
            return name;
        }

        function addNgClass(form, element, name, $compile, scope) {
            var isExistingNgClass = element[0].attributes["data-ng-class"] || element[0].attributes["ng-class"];
            if (!isExistingNgClass) {
                var ngClass = "{'has-error':" + form.$name + "." + name + ".$invalid && " +
                                "(" + form.$name + "." + name + ".$dirty || vm.submit), " +
                                "'has-success':" + form.$name + "." + name + ".$valid && " +
                                form.$name + "." + name + ".$dirty}";
                element.attr("data-ng-class", ngClass);
                $compile(element)(scope);
            }
        }

        function link($compile) {
            return function (scope, element, attrs, form) {
                var name = setupDom(element[0]);

                addNgClass(form, element, name, $compile, scope);
            }
        }

        return {
            restrict: 'A',
            require: '^form',
            link: link($compile)
        }
    }

}());

我将指令用作:

<div zz-forminput>
   <label for="firstName" class="col-md-4">First Name*</label>
   <div class="col-md-8">
       <input type="text" name="firstName" id="firstName" data-ng-model="vm.userDetails.firstName" required data-ng-maxlength="100">
   </div>
</div>

Angular 成功编译标记。当我在输入字段中输入任何文本时,没有将 has-success 添加到 div。但是当我清除文本框时,已成功 class 应用于 div。现在,当我在输入中输入一些文本时,已成功应用于 div。

请给我一个解决这个问题的方法

您看到这种奇怪行为的原因是您处理托管指令的元素内容的方式。

发生的事情是内容,包括输入的 ng-model 指令,被编译两次:一次是当 Angular 越过 DOM 时(在编译阶段),并且当您手动调用 $compile 服务时(在指令的链接阶段)。这会导致 ng-model 指令以相同的名称向父表单控制器注册两次,长话短说,会导致一些奇怪的情况。

处理内容的正确方法是使用 transclude 函数,提供给指令的 link 函数。

transclude: true,
link: function(scope, element, attrs, ctrls, transclude){
   transclude(scope, function(clone){
     element.append(clone); // clone is the clone of the contents, prebound to scope
   }
}

或者简单地说,通过模板使用 <div ng-transclude></div>,因为您不需要在那里做任何特别的事情

transclude: true,
template: '<div ng-transclude></div>`

但你甚至不需要做任何这些,因为你的指令只是在内容上放置一些 classes 并将一些 classes 应用于它自己,你正在尝试这样做使用 ng-class,这需要您使用 $compile。而不是这样做,只需 $watch 更改并直接应用 class:

link: function(scope, element, attrs, formCtrl){
  var inputName = setupDom(element[0]);

  scope.$watch(function(){
    return formCtrl[inputName].$valid && formCtrl[inputName].$dirty;
  }, function(v){
    if (v) element.addClass("has-success");
    else element.removeClass("has-success");
  });

  scope.$watch(function(){
    return formCtrl[inputName].$invalid && 
           (formCtrl[inputName].$dirty || formCtrl.$submitted);
  }, function(v){
    if (v) element.addClass("has-error");
    else element.removeClass("has-error");
  })
}

并且不需要$compiletransclude

plunker