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


95

我似乎无法找到一种无需使用隔离范围就可以在指令中从父范围调用函数的方法。我知道,如果我使用隔离作用域,则可以在隔离作用域中使用“&”来访问父作用域上的函数,但是在不必要时使用隔离作用域会产生后果。考虑以下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不再针对父范围执行,而是在隔离范围内执行(因为在任何指令上使用隔离范围都会导致该元素上的所有指令使用隔离范围)。这是上面示例的jsFiddle,其中ng-hide不起作用。(请注意,在此小提琴中,当您在输入框中键入“是”时,按钮应该隐藏。)

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

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

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

另外,如果其他指令仍然可以对父作用域起作用,那么我会很想听到使用隔离作用域的解决方案,但是我认为这是不可能的。


对于那些通过传递,丹Wahlin写了一篇很好的文章解释分离范围和功能参数:weblogs.asp.net/dwahlin/...
tanguy_k

Answers:


117

由于伪指令仅调用函数(而不是尝试在属性上设置值),因此可以使用$ eval代替$ parse(具有非隔离范围):

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

或更好的是,只需使用$ apply,这将使$ eval()将其参数反对作用域:

scope.$apply(attrs.confirmAction);

小提琴


不知道,对此表示感谢。我一直认为$ parse方法太罗word了。
克拉克潘

2
您将如何在该函数上设置参数?
CMCDragonkai 2013年

2
@CMCDragonkai,如果函数具有参数,则可以在HTML中指定它们confirm-action="doIt(arg1)",然后scope.arg1在调用$ eval之前进行设置。但是,使用$ parse可能会更干净:stackoverflow.com/a/16200618/215945
Mark Rajcok 2013年

$ eval(attrs.doIt({arg1:'blah'})
;;

3
@CMCDragonkai,不行scope.$eval(attrs.confirmAction({arg1: 'blah'})。您需要将$ parse与该语法一起使用。
Mark Rajcok 2013年

17

您想使用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,但未能通过范围,因此它对我不起作用。太棒了。
吉姆·库珀

我很惊讶地发现这种解决方案在没有scope:true的情况下也可以使用。我的理解是scope:true是使该指令的作用域原型继承自父作用域的原因。知道为什么没有它仍然可以工作吗?
Jim Cooper

2
没有任何子作用域(删除scope:true)起作用,因为该指令根本不会创建新作用域,因此,scope您在link函数中引用的属性与先前的“父”作用域完全相同。
克拉克潘

$ apply对于这种情况就足够了(请参阅我的答案)。
Mark Rajcok

1
$ event有什么用?
CMCDragonkai 2013年

12

显式调用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。但是,我将接受Clark的回答,因为该解决方案不需要我的指令的用户知道他们需要添加$ parent。感谢您的见解!
吉姆·库珀

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是因为解析函数的想法很棘手,并帮助我解决了类似但又另一个问题。谢谢!
Anmol Saraf
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.