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");
})
}
并且不需要$compile
或transclude
我制定了以下指令:
(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");
})
}
并且不需要$compile
或transclude