Вызов функции контроллера из директивы без изолированной области видимости в 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 () не работает, и я не знаю, как заставить его работать.

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

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

Джим Купер
источник
Для тех, кто проходит мимо, Дэн Валин написал очень хорошую статью, объясняющую область действия изолята и параметры функции: weblogs.asp.net/dwahlin/…
tanguy_k

Ответы:

117

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

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

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

scope.$apply(attrs.confirmAction);

Скрипка

Марк Райкок
источник
Не знал, что спасибо. Мне всегда казалось, что метод $ parse слишком многословен.
Clark Pan
2
как бы вы установили параметры для этой функции?
CMCDragonkai
2
@CMCDragonkai, если функция имеет аргументы, вы можете указать их в HTML confirm-action="doIt(arg1)", а затем установить scope.arg1перед вызовом $ eval. Однако, вероятно, было бы чище использовать $ parse: stackoverflow.com/a/16200618/215945
Марк Райкок
Невозможно сделать scope. $ Eval (attrs.doIt ({arg1: 'blah'}) ;?
CMCDragonkai
3
@CMCDragonkai, нет, scope.$eval(attrs.confirmAction({arg1: 'blah'})не пойдет. Вам нужно будет использовать $ parse с этим синтаксисом.
Mark Rajcok 05
17

Вы хотите использовать службу $ 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's scope.
                        fn(scope, {$event : e});
                    });
                }
            });
        }
    };
});

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

Кларк Пэн
источник
Спасибо, Кларк, это именно то, что я искал. Я пытался использовать $ parse, но не смог передать область видимости, поэтому у меня это не сработало. Это потрясающе.
Джим Купер
Я был удивлен, обнаружив, что это решение также работает без области действия: правда. Насколько я понимаю, эта область видимости: true - это то, что делает область действия директивы прототипически наследуемой от родительской области. Есть идеи, почему это все еще работает без него?
Джим Купер
2
никакая дочерняя область (удаление scope:true) не работает, потому что директива вообще не создает новую область видимости, и, таким образом, scopeсвойство, на которое вы ссылаетесь в linkфункции, имеет ту же область видимости, что и ранее «родительская» область.
Clark Pan
$ apply в этом случае достаточно (см. мой ответ).
Mark Rajcok
1
Для чего это событие $?
CMCDragonkai 05
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. Однако я собираюсь принять ответ Кларка, поскольку это решение не требует, чтобы пользователь моей директивы знал, что им нужно добавить $ 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]();
      }
    });
  }
7штук
источник
+1 так как идея синтаксического анализа функции была сложной и помогла мне с аналогичной, но другой проблемой. Спасибо!
Анмол Сараф