Вызовите функцию контроллера из директивы без изолированной области в AngularJS

Я не могу найти способ вызвать функцию в родительской области из директивы без использования изолированной области. Я знаю, что если я использую изолированный объем, я могу просто использовать "&". в изолированном доступе к функции в родительской области, но с использованием изолированной области, когда это не является необходимым, имеет последствия. Рассмотрим следующий 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() не работает, и я не знаю, как заставить его работать.

Обратите внимание, что я действительно ищу, как вызвать функции во внешнем пространстве БЕЗ использования изолированной области. И мне не интересно, чтобы это подтвердило работу диалога по-другому, потому что суть этого вопроса состоит в том, чтобы выяснить, как совершать вызовы внешней области и по-прежнему иметь возможность работать с другими директивами в отношении родительской области.

В качестве альтернативы мне было бы интересно узнать о решениях, которые используют изолированную область действия, если другие директивы будут по-прежнему работать против родительской области, но я не думаю, что это возможно.

Ответ 1

Поскольку директива вызывает только функцию (и не пытается установить значение для свойства), вы можете использовать $eval вместо $parse (с неизолированной областью):

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

Или лучше просто просто используйте $apply, который будет $eval() uate его аргумент против области:

scope.$apply(attrs.confirmAction);

Fiddle

Ответ 2

Вы хотите использовать службу $parse в AngularJS.

Итак, для вашего примера:

.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 scope.
                        fn(scope, {$event : e});
                    });
                }
            });
        }
    };
});

Существует вопрос, связанный с установкой свойства с помощью $parse, но я позволю вам пересечь этот мост, когда вы придете к нему.

Ответ 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>

Ответ 4

Единственная проблема, с которой я столкнулся, - как я могу вызвать метод в родительской области если я не передам его в автономном режиме?

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