在 angular 中的指令内添加属性指令
Add attribute directive inside directive in angular
我正在 angular 中创建一个验证指令,我需要向该指令绑定到的元素添加一个工具提示。
通过网络阅读我发现此解决方案为指令设置了高优先级和终端,但由于我使用的是 ngModel 这对我不起作用。这就是我现在正在做的事情:
return {
restrict: 'A',
require: 'ngModel',
replace: false,
terminal: true,
priority: 1000,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
element.attr('tooltip', '{{validationMessage}');
element.removeAttr("validator");
return {
post: function postLink(scope, element) {
$compile(element)(scope);
}
};
},
}
但这对我不起作用。它抛出以下错误:
Error: [$compile:ctreq] Controller 'ngModel', required by directive 'validator', can't be found!
这是我使用指令的 HTML:
<input id="username" name="username" data-ng-model="user.username" type="text" class="form-control" validator="required, backendWatchUsername" placeholder="johndoe" tabindex="1" >
关于如何解决这个问题的任何想法?
谢谢。
原因是因为您的指令 priority
与 terminal
选项的组合。这意味着 ngModel
指令根本不会呈现。由于您的指令优先级 (1000
) 大于 ng-model 的 (0
) 并且 terminal
选项的存在不会呈现任何其他优先级较低(低于 1000)的指令。所以一些可能的选择是:
- 从指令中删除终端选项或
- 将指令的优先级降低到 0 或 -1(小于或等于 ngModel)或
- 从指令中删除 ng-model 要求并可能使用双向绑定说
ngModel:"="
(根据您的要求)。
- 无需添加
tooltip
属性并重新编译元素,您可以在指令中使用嵌入并拥有一个指令模板。
terminal - If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined). Note that expressions and other directives used in the directive's template will also be excluded from execution.
演示
angular.module('app', []).directive('validator', function($compile) {
return {
restrict: 'A',
require: 'ngModel',
replace: false,
terminal: true,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
element.attr('tooltip', '{{validationMessage}');
element.removeAttr("validator");
return {
post: function postLink(scope, element) {
$compile(element)(scope);
}
};
},
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<input validator ng-model="test">
</div>
正如我在评论中所解释的那样,您不需要重新编译元素和所有这些东西,只需设置一个元素并将其附加在目标元素之后(在您的特定情况下,输入)。
这是验证指令的修改版本(我没有实现任何验证细节,我相信你应该能够轻松连接)。
所以您需要为工具提示设置自定义触发器,您可以使用 $tooltipprovider
来完成。因此,当您想要 show/hide 工具提示时设置一个事件对。
.config(function($tooltipProvider){
$tooltipProvider.setTriggers({'show-validation':'hide-validation'});
});
现在在您的指令中,只需根据需要设置工具提示元素,并在其上设置工具提示属性。仅编译工具提示元素并将其附加 after
目标元素(您当然可以使用 css 管理定位)。当您验证失败时,只需获取 tooltip element reference
(这是对工具提示元素的引用,而不是每次使用 select 或复制引用,您也可以 select )和做 $tooltipEl.triggerHandler('show-validation')
并隐藏它 $tooltipEl.triggerHandler('show-validation')
.
在 2 秒后显示工具提示并在 5 秒后隐藏它的示例实现(因为验证不在这个问题的范围内,你应该能够连接它):
.directive('validator', function($compile, $timeout){
var tooltiptemplate = '<span class="validation" tooltip="{{validationMessage}}" tooltip-trigger="show-validation" tooltip-placement="bottom"></span>';
var tooltipEvents = {true:'show-validation', false:'hide-validation'};
return {
restrict: 'A',
require: 'ngModel',
replace: false,
priority: 1000,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
return {
post: function postLink(scope, element) {
var $tooltipEl= getTooltip();
init();
function init(){
scope.$on('$destroy', destroy);
scope.validationMessage ="Whoops!!!";
$timeout(function(){
toggleValidationMessage(true);
},2000);
$timeout(function(){
toggleValidationMessage(false);
},5000);
}
function toggleValidationMessage(show){
$tooltipEl.triggerHandler(tooltipEvents[show]);
}
function getTooltip(){
var elm = $compile(angular.element(tooltiptemplate))(scope);
element.after(elm);
return elm;
}
function destroy(){
$tooltipEl= null;
}
}
};
},
}
});
内联演示
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('MainCtrl', function($scope) {
$scope.user = {
username: 'jack'
};
}).directive('validator', function($compile, $timeout) {
var tooltiptemplate = '<span class="validation" tooltip="{{model}}" tooltip-trigger="show-validation" tooltip-placement="bottom"></span>';
var tooltipEvents = {
true: 'show-validation',
false: 'hide-validation'
};
return {
restrict: 'A',
require: 'ngModel',
replace: false,
priority: 1000,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
return {
post: function postLink(scope, element) {
var $tooltipEl = getTooltip();
init();
function init() {
scope.$on('$destroy', destroy);
scope.validationMessage = "Whoops!!!";
$timeout(function() {
toggleValidationMessage(true);
}, 2000);
$timeout(function() {
toggleValidationMessage(false);
}, 5000);
}
function toggleValidationMessage(show) {
$tooltipEl.triggerHandler(tooltipEvents[show]);
}
function getTooltip() {
var elm = $compile(angular.element(tooltiptemplate))(scope);
element.after(elm);
return elm;
}
function destroy() {
elm = null;
}
}
};
},
}
}).config(function($tooltipProvider) {
$tooltipProvider.setTriggers({
'show-validation': 'hide-validation'
});
});
/* Put your css in here */
.validation {
display: block;
}
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap-css@3.1.*" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.12/angular.js" data-semver="1.3.12"></script>
<script data-require="ui-bootstrap@*" data-semver="0.12.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.0.min.js"></script>
</head>
<body ng-controller="MainCtrl">
<br/>
<br/>{{user.username}}
<input id="username" name="username" data-ng-model="user.username" type="text" class="form-control" validator="required, backendWatchUsername" placeholder="johndoe" tabindex="1">
</body>
</html>
你不应该在你的指令中创建一个新的独立范围:这会弄乱其他指令(并且在这种情况下不会共享 ngModel)。
return {
restrict: 'A',
require: 'ngModel',
compile: function compile(element, attrs) {
element.attr('tooltip', '{{validationMessage}');
element.removeAttr("validator");
return {
post: function postLink(scope, element) {
$compile(element)(scope);
}
};
},
}
我邀请您查看 Angular-UI 库,尤其是他们如何实施 ui.validate 指令:http://angular-ui.github.io/ui-utils/
我正在 angular 中创建一个验证指令,我需要向该指令绑定到的元素添加一个工具提示。
通过网络阅读我发现此解决方案为指令设置了高优先级和终端,但由于我使用的是 ngModel 这对我不起作用。这就是我现在正在做的事情:
return {
restrict: 'A',
require: 'ngModel',
replace: false,
terminal: true,
priority: 1000,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
element.attr('tooltip', '{{validationMessage}');
element.removeAttr("validator");
return {
post: function postLink(scope, element) {
$compile(element)(scope);
}
};
},
}
但这对我不起作用。它抛出以下错误:
Error: [$compile:ctreq] Controller 'ngModel', required by directive 'validator', can't be found!
这是我使用指令的 HTML:
<input id="username" name="username" data-ng-model="user.username" type="text" class="form-control" validator="required, backendWatchUsername" placeholder="johndoe" tabindex="1" >
关于如何解决这个问题的任何想法?
谢谢。
原因是因为您的指令 priority
与 terminal
选项的组合。这意味着 ngModel
指令根本不会呈现。由于您的指令优先级 (1000
) 大于 ng-model 的 (0
) 并且 terminal
选项的存在不会呈现任何其他优先级较低(低于 1000)的指令。所以一些可能的选择是:
- 从指令中删除终端选项或
- 将指令的优先级降低到 0 或 -1(小于或等于 ngModel)或
- 从指令中删除 ng-model 要求并可能使用双向绑定说
ngModel:"="
(根据您的要求)。 - 无需添加
tooltip
属性并重新编译元素,您可以在指令中使用嵌入并拥有一个指令模板。
terminal - If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined). Note that expressions and other directives used in the directive's template will also be excluded from execution.
演示
angular.module('app', []).directive('validator', function($compile) {
return {
restrict: 'A',
require: 'ngModel',
replace: false,
terminal: true,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
element.attr('tooltip', '{{validationMessage}');
element.removeAttr("validator");
return {
post: function postLink(scope, element) {
$compile(element)(scope);
}
};
},
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<input validator ng-model="test">
</div>
正如我在评论中所解释的那样,您不需要重新编译元素和所有这些东西,只需设置一个元素并将其附加在目标元素之后(在您的特定情况下,输入)。
这是验证指令的修改版本(我没有实现任何验证细节,我相信你应该能够轻松连接)。
所以您需要为工具提示设置自定义触发器,您可以使用 $tooltipprovider
来完成。因此,当您想要 show/hide 工具提示时设置一个事件对。
.config(function($tooltipProvider){
$tooltipProvider.setTriggers({'show-validation':'hide-validation'});
});
现在在您的指令中,只需根据需要设置工具提示元素,并在其上设置工具提示属性。仅编译工具提示元素并将其附加 after
目标元素(您当然可以使用 css 管理定位)。当您验证失败时,只需获取 tooltip element reference
(这是对工具提示元素的引用,而不是每次使用 select 或复制引用,您也可以 select )和做 $tooltipEl.triggerHandler('show-validation')
并隐藏它 $tooltipEl.triggerHandler('show-validation')
.
在 2 秒后显示工具提示并在 5 秒后隐藏它的示例实现(因为验证不在这个问题的范围内,你应该能够连接它):
.directive('validator', function($compile, $timeout){
var tooltiptemplate = '<span class="validation" tooltip="{{validationMessage}}" tooltip-trigger="show-validation" tooltip-placement="bottom"></span>';
var tooltipEvents = {true:'show-validation', false:'hide-validation'};
return {
restrict: 'A',
require: 'ngModel',
replace: false,
priority: 1000,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
return {
post: function postLink(scope, element) {
var $tooltipEl= getTooltip();
init();
function init(){
scope.$on('$destroy', destroy);
scope.validationMessage ="Whoops!!!";
$timeout(function(){
toggleValidationMessage(true);
},2000);
$timeout(function(){
toggleValidationMessage(false);
},5000);
}
function toggleValidationMessage(show){
$tooltipEl.triggerHandler(tooltipEvents[show]);
}
function getTooltip(){
var elm = $compile(angular.element(tooltiptemplate))(scope);
element.after(elm);
return elm;
}
function destroy(){
$tooltipEl= null;
}
}
};
},
}
});
内联演示
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('MainCtrl', function($scope) {
$scope.user = {
username: 'jack'
};
}).directive('validator', function($compile, $timeout) {
var tooltiptemplate = '<span class="validation" tooltip="{{model}}" tooltip-trigger="show-validation" tooltip-placement="bottom"></span>';
var tooltipEvents = {
true: 'show-validation',
false: 'hide-validation'
};
return {
restrict: 'A',
require: 'ngModel',
replace: false,
priority: 1000,
scope: {
model: '=ngModel',
initialValidity: '=initialValidity',
validCallback: '&',
invalidCallback: '&'
},
compile: function compile(element, attrs) {
return {
post: function postLink(scope, element) {
var $tooltipEl = getTooltip();
init();
function init() {
scope.$on('$destroy', destroy);
scope.validationMessage = "Whoops!!!";
$timeout(function() {
toggleValidationMessage(true);
}, 2000);
$timeout(function() {
toggleValidationMessage(false);
}, 5000);
}
function toggleValidationMessage(show) {
$tooltipEl.triggerHandler(tooltipEvents[show]);
}
function getTooltip() {
var elm = $compile(angular.element(tooltiptemplate))(scope);
element.after(elm);
return elm;
}
function destroy() {
elm = null;
}
}
};
},
}
}).config(function($tooltipProvider) {
$tooltipProvider.setTriggers({
'show-validation': 'hide-validation'
});
});
/* Put your css in here */
.validation {
display: block;
}
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap-css@3.1.*" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.12/angular.js" data-semver="1.3.12"></script>
<script data-require="ui-bootstrap@*" data-semver="0.12.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.0.min.js"></script>
</head>
<body ng-controller="MainCtrl">
<br/>
<br/>{{user.username}}
<input id="username" name="username" data-ng-model="user.username" type="text" class="form-control" validator="required, backendWatchUsername" placeholder="johndoe" tabindex="1">
</body>
</html>
你不应该在你的指令中创建一个新的独立范围:这会弄乱其他指令(并且在这种情况下不会共享 ngModel)。
return {
restrict: 'A',
require: 'ngModel',
compile: function compile(element, attrs) {
element.attr('tooltip', '{{validationMessage}');
element.removeAttr("validator");
return {
post: function postLink(scope, element) {
$compile(element)(scope);
}
};
},
}
我邀请您查看 Angular-UI 库,尤其是他们如何实施 ui.validate 指令:http://angular-ui.github.io/ui-utils/