在指令链接功能中动态添加ng-click

时间:2014-03-01 15:44:19

标签: javascript angularjs angularjs-directive

我正在尝试创建一个指令,允许将元素定义为可点击或不可点击,并且将定义如下:

<page is-clickable="true">
    transcluded elements...
</page>

我希望生成的HTML为:

<page is-clickable="true" ng-click="onHandleClick()">
    transcluded elements...
</page>

我的指令实现如下:

app.directive('page', function() {
    return {
        restrict: 'E',
        template: '<div ng-transclude></div>',
        transclude: true,
        link: function(scope, element, attrs) {
            var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;

            if (isClickable) {
                attrs.$set('ngClick', 'onHandleClick()');
            }

            scope.onHandleClick = function() {
                console.log('onHandleClick');
            };
        }
    };
});

我可以看到,添加新属性后,Angular不知道ng-click,因此它不会被触发。我尝试在设置属性后添加$compile,但它会导致无限链接/编译循环。

我知道如果onHandleClick()值为isClickable,我可以在true函数内部进行检查,但我很好奇如何动态添加{ng-click 1}}事件因为我可能需要使用多个其他ng-*指令来执行此操作,并且我不想添加不必要的开销。有什么想法吗?

7 个答案:

答案 0 :(得分:28)

更好的解决方案(新):

阅读Angular docs之后,我发现了这个:

  

您可以将模板指定为表示模板的字符串或指定为       带有两个参数tElement和tAttrs的函数(在中描述)       下面的编译函数api)并返回表示的字符串值       模板。

所以我的新指令看起来像这样:(我相信这是适当的“Angular”方式来处理这类事情)

app.directive('page', function() {
    return {
        restrict: 'E',
        replace: true,
        template: function(tElement, tAttrs) {
            var isClickable = angular.isDefined(tAttrs.isClickable) && eval(tAttrs.isClickable) === true ? true : false;

            var clickAttr = isClickable ? 'ng-click="onHandleClick()"' : '';

            return '<div ' + clickAttr + ' ng-transclude></div>';
        },
        transclude: true,
        link: function(scope, element, attrs) {
            scope.onHandleClick = function() {
                console.log('onHandleClick');
            };
        }
    };
});

注意新的模板功能。现在我在编译之前操作该函数内的模板。

替代解决方案(旧):

添加replace: true以在重新编译指令时消除无限循环问题。然后在链接函数中,我只需在添加新属性后重新编译元素。有一点需要注意,因为我的元素上有一个ng-transclude指令,我需要删除它,所以它不会尝试在第二次编译时转换任何内容,因为没有什么可以转换。

这就是我的指令现在的样子:

app.directive('page', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div ng-transclude></div>',
        transclude: true,
        link: function(scope, element, attrs) {
            var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;

            if (isClickable) {
                attrs.$set('ngClick', 'onHandleClick()');
                element.removeAttr('ng-transclude');
                $compile(element)(scope);
            }

            scope.onHandleClick = function() {
                console.log('onHandleClick');
            };
        }
    };
});

我不认为第二次重新编译模板是理想的,所以我觉得在第一次编译模板之前还有一种方法可以做到这一点。

答案 1 :(得分:15)

您可以随时修改ng-click,如下所示:

ng-click="isClickable && someFunction()"

不需要自定义指令:)

这是一个JSFiddle演示它:http://jsfiddle.net/robianmcd/5D4VR/

答案 2 :(得分:3)

更新回答

“Angular Way”根本不是手动DOM操作。因此,我们需要摆脱添加和删除属性。

DEMO

将模板更改为:

template: '<div ng-click="onHandleClick()" ng-transclude></div>'

在指令中检查isClickable属性以决定点击时该做什么:

    link: function(scope, element, attrs) {
        var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;

        scope.onHandleClick = function() {
            if (!isClickable) return;
            console.log('onHandleClick');
        };
    }

您还可以将isClickable属性放在指令范围内,以便它可以动态地更改其行为。

旧答案(错误)

编译模板后运行

link。在编译之前使用controller对模板进行更改:

app.directive('page', function() {
    return {
        restrict: 'E',
        template: '<div ng-transclude></div>',
        transclude: true,
        controller: function(scope, element, attrs) {
            // your code
        }
    };
});

答案 3 :(得分:2)

<强> HTML

<div page is-clickable="true">hhhh</div>

<强> JS

app.directive('page', function($compile) {
                return {
                    priority:1001, // compiles first
                    terminal:true, // prevent lower priority directives to compile after it
                    template: '<div ng-transclude></div>',
                    transclude: true,
                    compile: function(el,attr,transclude) {
                        el.removeAttr('page'); // necessary to avoid infinite compile loop
                        var contents = el.contents().remove();
                        var compiledContents;
                        return function(scope){
                            var isClickable = angular.isDefined(attr.isClickable)?scope.$eval(attr.isClickable):false;
                            if(isClickable){
                                el.attr('ng-click','onHandleClick()');
                                var fn = $compile(el);
                                fn(scope);
                                scope.onHandleClick = function() {
                                    console.log('onHandleClick');
                                };
                            }
                            if(!compiledContents) {
                                compiledContents = $compile(contents, transclude);
                            }
                            compiledContents(scope, function(clone, scope) {
                                el.append(clone); 
                            });

                        };
                    },
                    link:function(scope){

                    }


                };
            });

归功于Erstad.StephenIlan Frumer

BTW with restrict:'E'浏览器崩溃:(

答案 4 :(得分:1)

这是我的@DiscGolfer解决方案版本,我也添加了对属性的支持。

.directive("page", function() {

  return {
    transclude: true,
    replace: true,
    template: function(tElement, tAttr) {

      var isClickable = angular.isDefined(tAttrs.isClickable) && eval(tAttrs.isClickable) === true ? true : false;

      if (isClickable) {
        tElement.attr("ng-click", "onHandleClick()");
      }
      tElement.attr("ng-transclude", "");
      if (tAttr.$attr.page === undefined) {
        return "<" + tElement[0].outerHTML.replace(/(^<\w+|\w+>$)/g, 'div') + ">";
      } else {
        tElement.removeAttr(tAttr.$attr.page);
        return tElement[0].outerHTML;
      }
    }

  };

提供了更通用的完整示例http://plnkr.co/edit/4PcMnpq59ebZr2VrOI07?p=preview

此解决方案的唯一问题是AngularJS中不推荐使用replace

答案 5 :(得分:0)

我认为它应该更好:

app.directive('page', function() {
    return {
        restrict: 'E',
        template: '<div ng-transclude></div>',
        transclude: true,
        link: function(scope, element, attrs) {
            var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;

            if (isClickable) {
                angular.element(element).on('click', scope.onHandleClick);
            }

            scope.onHandleClick = function() {
                console.log('onHandleClick');
            };
        }
    };
});

答案 6 :(得分:-1)

module.factory("ibDirectiveHelpers", ["ngClickDirective", function (ngClick) {
        return {
            click: function (scope, element, fn) {
                var attr = {ngClick: fn};
                ngClick[0].compile(element, attr)(scope, element, attr);
            }
        };
    }]);

使用:

module.controller("demoController",["$scope","$element","ibDirectiveHelpers",function($scope,$element,ibDirectiveHelpers){

$scope.demoMethod=function(){console.log("demoMethod");};
ibDirectiveHelpers.click($scope,$element,"demoMethod()");//uses html notation
 //or
ibDirectiveHelpers.click($scope,$element,function(){$scope.demoMethod();});//uses inline notation
}]