在 AngularJS 中从没有隔离范围的指令调用控制器函数

Posted

技术标签:

【中文标题】在 AngularJS 中从没有隔离范围的指令调用控制器函数【英文标题】:Call a controller function from a directive without isolated scope in AngularJS 【发布时间】:2013-07-09 02:42:08 【问题描述】:

如果不使用隔离作用域,我似乎无法找到一种方法来从指令内调用父作用域上的函数。我知道如果我使用隔离范围,我可以在隔离中使用“&”来访问父范围上的函数,但是在不需要时使用隔离范围会产生后果。考虑以下 html

<button ng-hide="hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button>

在这个简单的示例中,我想显示一个 javascript 确认对话框,并且仅当他们在确认对话框中单击“确定”时才调用 doIt()。使用隔离范围很简单。该指令如下所示:

.directive('confirm', function () 
    return 
        restrict: 'A',
        scope: 
            confirm: '@',
            confirmAction: '&'
        ,
        link: function (scope, element, attrs) 
            element.bind('click', function (e) 
                if (confirm(scope.confirm)) 
                    scope.confirmAction();
                
            );
        
    ;
)

但问题是,因为我使用的是隔离作用域,所以上面示例中的 ng-hide 不再针对父作用域执行,而是在隔离作用域中执行(因为使用了隔离作用域on any 指令会导致该元素上的所有指令使用隔离范围)。 Here is a jsFiddle 上面例子中的 ng-hide 不起作用。 (请注意,在这个小提琴中,当您在输入框中键入“yes”时,按钮应该隐藏。)

另一种选择是不使用隔离范围,这实际上是我真正想要的,因为不需要隔离该指令的范围。我遇到的唯一问题是,如果我不在隔离范围内传递父范围内的方法,如何调用它

Here is a jsfiddle 我没有使用隔离作用域并且 ng-hide 工作正常,但是,对 confirmAction() 的调用当然不起作用,我不知道如何使它工作。

请注意,我真正想要的答案是如何在不使用隔离作用域的情况下调用外部作用域上的函数。而且我对让这个确认对话框以另一种方式工作不感兴趣,因为这个问题的重点是弄清楚如何调用外部作用域,并且仍然能够让其他指令对父作用域起作用。

或者,如果其他指令仍然可以针对父作用域工作,我很想听听使用隔离作用域的解决方案,但我认为这是不可能的。

【问题讨论】:

对于路过的人,Dan Wahlin 写了一篇很好的文章,解释了隔离作用域和函数参数:weblogs.asp.net/dwahlin/… 【参考方案1】:

我唯一的问题是,如何在父作用域上调用方法 如果我不在隔离范围内传递它?

这是一个 jsfiddle,我没有使用隔离范围和 ng-hide 工作正常,但是,当然,对 confirmAction() 的调用没有 工作,我不知道如何使它工作。

首先要注意一点:在这种情况下,外部控制器的作用域不是指令作用域的父作用域;外部控制器的范围指令的范围。换句话说,指令中使用的变量名将直接在控制器的作用域中查找。

接下来,写attrs.confirmAction() 不起作用,因为这个按钮是attrs.confirmAction

<button ... confirm-action="doIt()">Do It</button>

是字符串"doIt()",不能调用字符串,例如"doIt()"().

你的问题确实是:

当我将函数名称作为字符串时,如何调用函数?

在 JavaScript 中,您大多使用点符号来调用函数,如下所示:

some_obj.greet()

但你也可以使用数组表示法:

some_obj['greet']

注意索引值是字符串。因此,在您的指令中,您可以简单地执行以下操作:

  link: function (scope, element, attrs) 

    element.bind('click', function (e) 

      if (confirm(attrs.confirm)) 
        var func_str = attrs.confirmAction;
        var func_name = func_str.substring(0, func_str.indexOf('(')); // Or func_str.match(/[^(]+/)[0];
        func_name = func_name.trim();

        console.log(func_name); // => doIt
        scope[func_name]();
      
    );
  

【讨论】:

+1 因为解析函数的想法很棘手,并且帮助我解决了类似但另一个问题。谢谢!【参考方案2】:

你想在 AngularJS 中使用 $parse 服务。

所以对于你的例子:

.directive('confirm', function ($parse) 
    return 
        restrict: 'A',

        // Child scope, not isolated
        scope : true,
        link: function (scope, element, attrs) 
            element.bind('click', function (e) 
                if (confirm(attrs.confirm)) 
                    scope.$apply(function()

                        // $parse returns a getter function to be 
                        // executed against an object
                        var fn = $parse(attrs.confirmAction);

                        // In our case, we want to execute the statement in 
                        // confirmAction i.e. 'doIt()' against the scope 
                        // which this directive is bound to, because the 
                        // scope is a child scope, not an isolated scope. 
                        // The prototypical inheritance of scopes will mean 
                        // that it will eventually find a 'doIt' function 
                        // bound against a parent's scope.
                        fn(scope, $event : e);
                    );
                
            );
        
    ;
);

使用 $parse 设置属性有一个问题,但当你遇到它时,我会让你过桥。

【讨论】:

谢谢,克拉克,这正是我想要的。我曾尝试使用 $parse,但未能传递范围,因此它对我不起作用。这是完美的。 我惊讶地发现这个解决方案在没有范围的情况下也可以工作:true。我的理解是 scope:true 是什么使指令的范围原型继承自父范围。知道为什么没有它仍然可以工作吗? 没有子作用域(删除scope:true)有效,因为该指令根本不会创建新作用域,因此您在link 函数中引用的scope 属性是完全相同的作用域以前作为“父”范围。 $apply 对于这种情况就足够了(见我的回答)。 $event 有什么用?【参考方案3】:

在父作用域上显式调用hideButton

这是小提琴:http://jsfiddle.net/pXej2/5/

这是更新后的 HTML:

<div ng-app="myModule" ng-controller="myController">
    <input ng-model="showIt"></input>
    <button ng-hide="$parent.hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button>
</div>

【讨论】:

+1 表示可行的解决方案。我实际上尝试过使用 $parent 并且当它不起作用时放弃了。在看到您的解决方案后,我仔细研究了一下,结果发现我未能将 $parent 添加到我传入的参数中(未在我的原始小提琴中显示)。例如,如果我想将 showIt 传递给 hideButton() 函数,我需要使用 $parent.hideButton($parent.showIt) 而我错过了第二个 $parent。但是,我将接受克拉克的回答,因为该解决方案不需要我的指令的用户知道他们需要添加 $parent。感谢您的洞察力!【参考方案4】:

由于该指令仅调用一个函数(而不是尝试为属性设置值),您可以使用 $eval 而不是 $parse(具有非隔离范围):

scope.$apply(function() 
    scope.$eval(attrs.confirmAction);
);

或者更好的是,只需使用$apply,它将 $eval() 将其参数与作用域相匹配:

scope.$apply(attrs.confirmAction);

Fiddle

【讨论】:

不知道,谢谢。我一直觉得 $parse 方法有点啰嗦。 你将如何设置该函数的参数? @CMCDragonkai,如果函数有参数,你可以在HTML中指定它们,confirm-action="doIt(arg1)",然后在调用$eval之前设置scope.arg1。但是,使用 $parse 可能会更干净:***.com/a/16200618/215945 不可能做范围.$eval(attrs.doIt(arg1: 'blah');? @CMCDragonkai,不,scope.$eval(attrs.confirmAction(arg1: 'blah') 不起作用。您需要使用 $parse 与该语法。

以上是关于在 AngularJS 中从没有隔离范围的指令调用控制器函数的主要内容,如果未能解决你的问题,请参考以下文章

为啥我应该在 AngularJS 中使用隔离范围指令?

AngularJS中带有ng-repeat范围的指令隔离范围

如何在 AngularJS 中对隔离范围指令进行单元测试

AngularJS自定义验证指令 - 如何避免使用隔离范围

为什么多个指令不能在同一个元素上要求隔离范围?

0