Викликати функцію контролера з директиви без ізольованого обсягу в AngularJS


95

Здається, я не можу знайти спосіб викликати функцію в батьківській області зсередини директиви, не використовуючи ізольовану область. Я знаю, що якщо я використовую ізольовану область, я можу просто використовувати "&" в ізольованій, щоб отримати доступ до функції батьківської області, але використання ізольованої області, коли це не потрібно, має наслідки. Розгляньте такий HTML:

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

У цьому простому прикладі я хочу показати діалогове вікно підтвердження JavaScript і викликати doIt () лише тоді, коли вони натискають "OK" у діалоговому вікні підтвердження. Це просто, використовуючи ізольований обсяг. Директива буде виглядати так:

.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 працює нормально, але, звичайно, виклик для підтвердження дії () не працює, і я не знаю, як змусити його працювати.

Зверніть увагу, відповідь, яку я справді шукаю, - це як викликати функції на зовнішній області БЕЗ використання ізольованої області. І я не зацікавлений в тому, щоб це діалогове вікно підтвердження працювало по-іншому, оскільки сенс цього питання полягає в тому, щоб зрозуміти, як здійснювати виклики до зовнішньої області, і при цьому мати можливість, щоб інші директиви працювали проти батьківської області.

Як варіант, мені було б цікаво почути рішення, які використовують ізольований обсяг, якщо інші директиви все ще будуть працювати проти батьківського обсягу , але я не думаю, що це можливо.


Для тих, хто проходить повз, Ден Уолін написав дуже гарну статтю, в якій пояснює ізоляцію обсягу та параметрів функції: 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. Однак, ймовірно , було б чистий використовувати $ розбору: stackoverflow.com/a/16200618/215945
Mark Rajcok

Чи неможливо зробити сферу. $ Eval (attrs.doIt ({arg1: 'blah'})) ;?
CMCDragonkai

3
@CMCDragonkai, ні, scope.$eval(attrs.confirmAction({arg1: 'blah'})не буде працювати. Вам потрібно буде використовувати $ parse з цим синтаксисом.
Mark Rajcok

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. Я зрозумів, що область дії: true - це те, що змушує область застосування директиви прототипно успадковуватись від батьківської області. Будь-яка ідея, чому це все ще працює без цього?
Джим Купер,

2
жодна дочірня область (видалення scope:true) не працює, оскільки директива взагалі не створить нову область, і, отже, scopeвластивість, на яку ви посилаєтесь у linkфункції, є точно такою ж сферою, як і "батьківська" область раніше.
Clark Pan

Для цього випадку достатньо $ apply (див. мою відповідь).
Mark Rajcok

1
Для чого $ подія?
CMCDragonkai

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. Дякую за розуміння!
Джим Купер

1

Єдина проблема у мене полягає в тому, як мені викликати метод у батьківській області, якщо я не передаю його в ізольовану область?

Ось jsfiddle, де я НЕ використовую ізольовану область дії, а ng-hide працює нормально, але, звичайно, виклик для підтвердження дії () не працює, і я не знаю, як змусити його працювати.

Спочатку невеликий момент: У цьому випадку сфера зовнішнього контролера не є батьківською сферою сфери дії директиви; область зовнішнього контролера - сферою Директиви. Іншими словами, імена змінних, що використовуються в директиві, будуть шукатися безпосередньо в області дії контролера.

Далі, написання 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, оскільки ідея синтаксичного аналізу функції була хитра і допомогла мені з подібною, але іншою проблемою. Дякую!
Анмол Сараф,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.