在Angular中的指令内添加属性指令

3

我正在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);
                }
            };
        },
}

但是对我来说没有用。它抛出了以下错误:

错误:[$compile:ctreq] 找不到指令 'validator' 所需的控制器'ngModel'!

这是我在使用该指令的 HTML 代码:

<input id="username" name="username" data-ng-model="user.username" type="text" class="form-control" validator="required, backendWatchUsername" placeholder="johndoe" tabindex="1" >

有什么想法可以解决这个问题吗?

谢谢。


你能让我们看一下你使用指令的HTML代码吗? - floribon
嗨 @floribon,已将HTML代码添加到问题中。谢谢 - richardalberto
2个回答

5
由于您的指令 priority 结合了 terminal 选项,这就是为什么 ngModel 指令根本不会渲染的原因。因为您的指令优先级(1000)大于 ng-model 的优先级(0),而且存在 terminal 选项将不会渲染任何其他优先级低于 1000 的指令。所以有一些可能的选择:

  • 从您的指令中删除 terminal 选项或
  • 将您的指令优先级降低到 0 或 -1(小于等于 ngModel)或
  • 从指令中删除 ng-model 要求,并可能使用双向绑定,例如 ngModel:"="(根据您的要求)。
  • 除了添加 tooltip 属性并重新编译元素之外,您可以在指令中使用 transclusion 并具有指令模板。
终端 - 如果设置为true,则当前优先级将是最后执行的指令集(任何当前优先级的指令仍将执行,因为同一优先级的执行顺序未定义)。请注意,指令模板中使用的表达式和其他指令也将被排除在执行之外。 演示

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 来完成。因此,在您想要显示/隐藏工具提示时设置一个事件对。

.config(function($tooltipProvider){
    $tooltipProvider.setTriggers({'show-validation':'hide-validation'});
});

在您的指令中,只需将提示工具元素设置为您喜欢的样式,并在其上设置提示工具属性。只编译提示工具元素并将其附加到目标元素之后(当然可以使用css进行定位)。当验证失败时,只需获取提示工具元素的引用(这是对提示工具元素的引用,而不是复制引用,您也可以每次使用选择器进行选择),然后执行$tooltipEl.triggerHandler('show-validation')进行显示,执行$tooltipEl.triggerHandler('hide-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;
                 }

                }
            };
        },
  }

});

Plnkr

内嵌演示

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>


@RichardGonzálezAlberto 我已经在我的回答中粘贴了可能的选项。 - PSL
1
在链接函数中,定义作用域属性 ="ngModel" 或使用所需的 ngModel 控制器更好? - lujcon
@RichardGonzalez 你说你已经尝试了所有这些,是什么问题?当你增加优先级并使用终端选项时,你需要注意(正如我在我的回答中所解释的)。你为什么需要终端选项呢? - PSL
@RichardGonzalez 请看我上面的评论。 - PSL
@RichardGonzalez 你在使用angular ui bs tooltip吗?是的,你说得对。你有没有偶然设置好一个plunker?我回到家后会看一下。你想在输入无效值时通过手动触发来显示tooltip吗? - PSL
显示剩余9条评论

0
你不应该在指令中创建新的隔离作用域:这会影响其他指令(如果这样做,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/


这并没有解决我遇到的主要问题,即一旦我将该指令删除,它就会失去功能。但还是谢谢你的回复。 - richardalberto
你说的是一旦移除它,会发生什么? - floribon
当您执行以下操作时:element.removeAttr("validator"); 您正在删除该指令,以便它不会进入无限循环。但是,这也会移除该指令的功能。 - richardalberto
哦,哇,我没有注意到那个!这是一个非常非常糟糕的想法。编译方法只会被调用一次,如果它不被调用,你就有另一个问题了。至少检查指令是否已经被调用,通过向元素添加一些数据或类或任何东西,但不要这样做。 - floribon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接