从 AngularJS 中没有隔离作用域的指令调用控制器函数

如果不使用隔离作用域,我似乎找不到从指令内部调用父作用域上的函数的方法。我知道如果我使用隔离作用域,我可以只在隔离作用域中使用“ &”来访问父作用域上的函数,但是在不必要时使用隔离作用域会产生后果。考虑下面的 HTML:

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

在这个简单的示例中,我希望显示一个 JavaScript 确认对话框,只有在确认对话框中单击“ OK”时才调用 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 不再针对父作用域 执行,而是在隔离作用域中执行(因为对任何指令使用隔离作用域会导致该元素上的所有指令都使用隔离作用域)。上面例子中 ng- 隐藏不起作用的 这是一个 jsFiddle。(注意,在这个小提琴中,当你在输入框中键入“ yes”时,按钮应该隐藏起来。)

另一种选择是 不要使用隔离作用域,这实际上是我在这里真正想要的,因为不需要隔离这个指令的作用域。我唯一的问题是 如果我不在隔离范围中传递方法,我怎么在父范围中调用它

这里有一个 jsfiddle ,其中我没有使用隔离作用域,而 ng-hide 工作得很好,但是,当然,对 firmAction ()的调用不工作,我不知道如何让它工作。

请注意,我真正寻找的答案是如何在不使用隔离作用域的情况下调用外部作用域上的函数。我对这个确认对话框以另一种方式工作不感兴趣,因为这个问题的关键是弄清楚如何调用外部作用域,同时还能让其他指令在父作用域中工作。

或者,如果其他指令仍然与父作用域 相对应,那么我有兴趣了解使用独立作用域的解决方案,但我认为这是不可能的。

91647 次浏览

Explicitly call hideButton on the parent scope.

Here's the fiddle: http://jsfiddle.net/pXej2/5/

And here is the updated 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>

You want to make use of the $parse service in AngularJS.

So for your example:

.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});
});
}
});
}
};
});

There is a gotcha to do with setting a property using $parse, but I'll let you cross that bridge when you come to it.

Since the directive is only calling a function (and not trying to set a value on a property), you can use $eval instead of $parse (with a non-isolated scope):

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

Or better, simply just use $apply, which will $eval()uate its argument against the scope:

scope.$apply(attrs.confirmAction);

Fiddle

The only problem I have is, how do I call a method on the parent scope if I don't pass it in on on isolated scope?

Here is a jsfiddle where I am NOT using isolated scope and the ng-hide is working fine, but, of course, the call to confirmAction() doesn't work and I don't know how to make it work.

First a small point: In this case the outer controller's scope is not the parent scope of the directive's scope; the outer controller's scope is the directive's scope. In other words, variable names used in the directive will be looked up directly in the controller's scope.

Next, writing attrs.confirmAction() doesn't work because attrs.confirmAction for this button,

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

is the string "doIt()", and you can't call a string, e.g. "doIt()"().

Your question really is:

How do I call a function when I have its name as a string?

In JavaScript, you mostly call functions using dot notation, like this:

some_obj.greet()

But you can also use array notation:

some_obj['greet']

Note how the index value is a string. So, in your directive you can simply do this:

  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]();
}
});
}