Як викликати метод, визначений у директиві AngularJS?


297

У мене є директива, ось код:

.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {

            var center = new google.maps.LatLng(50.1, 14.4); 
            $scope.map_options = {
                zoom: 14,
                center: center,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            // create map
            var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
            var dirService= new google.maps.DirectionsService();
            var dirRenderer= new google.maps.DirectionsRenderer()

            var showDirections = function(dirResult, dirStatus) {
                if (dirStatus != google.maps.DirectionsStatus.OK) {
                    alert('Directions failed: ' + dirStatus);
                    return;
                  }
                  // Show directions
                dirRenderer.setMap(map);
                //$scope.dirRenderer.setPanel(Demo.dirContainer);
                dirRenderer.setDirections(dirResult);
            };

            // Watch
            var updateMap = function(){
                dirService.route($scope.dirRequest, showDirections); 
            };    
            $scope.$watch('dirRequest.origin', updateMap);

            google.maps.event.addListener(map, 'zoom_changed', function() {
                $scope.map_options.zoom = map.getZoom();
              });

            dirService.route($scope.dirRequest, showDirections);  
        }
    }
})

Я хотів би закликати updateMap()до дії користувача. Кнопка дії не вказана в директиві.

Який найкращий спосіб зателефонувати updateMap()з контролера?


11
Невелика бічна зауваження: угода не передбачає використання знака долара для "області" у функції зв'язку, оскільки область не вводиться, а передається як звичайний аргумент.
Ноам

Відповіді:


369

Якщо ви хочете використовувати окремі області дії, ви можете передати об'єкт управління за допомогою двостороннього прив'язки =змінної з області контролера. Ви також можете керувати також декількома екземплярами однієї директиви на сторінці з тим самим об'єктом управління.

angular.module('directiveControlDemo', [])

.controller('MainCtrl', function($scope) {
  $scope.focusinControl = {};
})

.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{internalControl}}</div>',
    scope: {
      control: '='
    },
    link: function(scope, element, attrs) {
      scope.internalControl = scope.control || {};
      scope.internalControl.takenTablets = 0;
      scope.internalControl.takeTablet = function() {
        scope.internalControl.takenTablets += 1;
      }
    }
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
  <div ng-controller="MainCtrl">
    <button ng-click="focusinControl.takeTablet()">Call directive function</button>
    <p>
      <b>In controller scope:</b>
      {{focusinControl}}
    </p>
    <p>
      <b>In directive scope:</b>
      <focusin control="focusinControl"></focusin>
    </p>
    <p>
      <b>Without control object:</b>
      <focusin></focusin>
    </p>
  </div>
</div>


11
+1 Так само я створюю API для своїх багаторазових компонентів у Angular.
romiem

5
Це чистіше, ніж прийнята відповідь, і +1 для посилань на Simpsons, якщо я не помиляюся
Блейк Міллер

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

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

4
Ви, ймовірно, повинні зробити перевірку, щоб переконатися, що scope.controlіснує, інакше інші місця, які використовують директиву, але не потребують доступу до методів директиви та не мають controlattr, почнуть видавати помилки щодо неможливості встановлення атрибутів наundefined
CheapSteaks

73

Якщо припустити, що кнопка дії використовує той же контролер $scope, що і директива, просто визначте функцію, що updateMapзнаходиться $scopeвсередині функції посилання. Потім ваш контролер може викликати цю функцію, коли натиснути кнопку дії.

<div ng-controller="MyCtrl">
    <map></map>
    <button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {
            $scope.updateMap = function() {
                alert('inside updateMap()');
            }
        }
    }
});

fiddle


Відповідно до коментаря @ FlorianF, якщо директива використовує окрему сферу дії, все складніше. Ось один із способів змусити його працювати: додати set-fnатрибут до mapдирективи, який зареєструє функцію директиви у контролері:

<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope: { setFn: '&' },
link: function(scope, element, attrs) {
    scope.updateMap = function() {
       alert('inside updateMap()');
    }
    scope.setFn({theDirFn: scope.updateMap});
}
function MyCtrl($scope) {
    $scope.setDirectiveFn = function(directiveFn) {
        $scope.directiveFn = directiveFn;
    };
}

fiddle


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

Дякую! (Можливо, було б простіше викликати функцію, визначену в контролері директиви, але я не впевнений у цьому)
Флоріан F

1
Це набагато кращий спосіб, якщо ви не маєте справу з ізольованою сферою.
Мартін Франк

Ця відповідь насправді відповідає на питання ОП. Він також використовує ізольований обсяг, щоб мати ізольований обсяг, вам потрібно лише додати scopeвластивість до декларації директиви.
Даніель Г.

35

Хоча може виявитися спокусливим виставити об’єкт на окрему область директиви, щоб полегшити спілкування з ним, це може призвести до заплутування коду "спагетті", особливо якщо вам потрібно зв’язати це повідомлення через пару рівнів (контролер, директива, до вкладеної директиви тощо)

Спочатку ми пішли по цьому шляху, але після проведення додаткових досліджень виявило, що це має більше сенсу і призвело до того, що і більш доступний і читабельний код розкриває події та властивості, які директива використовуватиме для спілкування через сервіс, а потім використовує $ watch на властивості цієї служби в директива або будь-яке інше управління, яке потрібно було б реагувати на ці зміни для спілкування.

Ця абстракція дуже добре працює з рамкою введення залежності AngularJS, оскільки ви можете ввести послугу в будь-які предмети, які потребують реакції на ці події. Якщо ви подивитесь на файл Angular.js, ви побачите, що директиви в ньому також використовують сервіси та $ watch таким чином, вони не піддають події над ізольованою сферою.

Нарешті, у випадку, якщо вам потрібно спілкуватися між директивами, які залежать одна від одної, я б рекомендував ділитися контролером між цими директивами як засобом зв'язку.

AnglisJS's Wiki for Best Practices також згадує про це:

Використовуйте лише. $ Broadcast (),. $ Emit () та. $ On () для атомних подій, які є актуальними у всьому світі у всьому додатку (наприклад, аутентифікація користувача або закриття програми). Якщо ви хочете, щоб події були специфічними для модулів, служб або віджетів, вам варто врахувати послуги, контролери директив або сторонні бібліотеки

  • $ range. $ watch () має замінити потребу в подіях
  • Ін'єкція послуг та методів виклику безпосередньо також корисна для прямого спілкування
  • Директиви можуть безпосередньо спілкуватися між собою через контролери-директори

2
Я дійшов до двох рішень інтуїтивно: (1) спостерігати за зміною змінної області =, змінна містить ім'я методу та аргументи. (2) виставити односторонній зв'язок @як ідентифікатор теми та дозволити callee надсилати подію на цю тему. Тепер я побачив найкращі вікі-практики. Я думаю, що є причина не робити цього, можливо. Але мені ще не дуже зрозуміло, як це працює. У моєму випадку я створив директиву набірних таблиць, я хочу викрити switchTab(tabIndex)метод. Не могли б ви прикласти більше?
stanleyxu2005

Ви б не викривали switchTab(tabIndex)метод, ви прив'язували б лише до tabIndexзмінної. Ваш контролер сторінки може мати дії, які змінюють цю змінну. Ви прив'язуєте / передаєте цю змінну у директиву вкладки. Потім директива вашої вкладки може спостерігати за цією змінною за змінами та виконувати перемикач Tab за власним бажанням. Тому що директива визначає, коли / як керувати своїми вкладками на основі змінної. Це не робота зовнішнього джерела, інакше зовнішні джерела вимагають знання внутрішньої роботи директиви, що погано.
Суамер

15

Спираючись на відповідь Олівера - можливо, вам не завжди потрібно мати доступ до внутрішніх методів директиви, і в тих випадках вам, мабуть, не потрібно створювати порожній об’єкт і додавати controlattr до директиви лише для того, щоб запобігти появі помилки (cannot set property 'takeTablet' of undefined ).

Ви також можете використовувати метод в інших місцях директиви.

Я би додав чек, щоб переконатися, що scope.controlіснує, і встановив би методи таким чином, як розкриваючий шаблон модуля

app.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{control}}</div>',
    scope: {
      control: '='
    },
    link : function (scope, element, attrs) {
      var takenTablets = 0;
      var takeTablet = function() {
        takenTablets += 1;  
      }

      if (scope.control) {
        scope.control = {
          takeTablet: takeTablet
        };
      }
    }
  };
});

Виявлення, використання виявленого зразка всередині директиви робить наміри набагато зрозумілішими. хороший!
JSancho

12

Якщо чесно, я не дуже переконався ні в одній з відповідей у ​​цій темі. Отже, ось мої рішення:

Підхід до директора (менеджера)

Цей метод є агностичним щодо директивних $scope загальною чи ізольованою

A factoryдля реєстрації екземплярів директиви

angular.module('myModule').factory('MyDirectiveHandler', function() {
    var instance_map = {};
    var service = {
        registerDirective: registerDirective,
        getDirective: getDirective,
        deregisterDirective: deregisterDirective
    };

    return service;

    function registerDirective(name, ctrl) {
        instance_map[name] = ctrl;
    }

    function getDirective(name) {
        return instance_map[name];
    }

    function deregisterDirective(name) {
        instance_map[name] = null;
    }
});

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

angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {
    var directive = {
        link: link,
        controller: controller
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        var name = $attrs.name;

        this.updateMap = function() {
            //some code
        };

        MyDirectiveHandler.registerDirective(name, this);

        $scope.$on('destroy', function() {
            MyDirectiveHandler.deregisterDirective(name);
        });
    }
})

код шаблону

<div my-directive name="foo"></div>

Доступ до екземпляра контролера за допомогою factoryметодів & запустити відкрито відкриті

angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {
    $scope.someFn = function() {
        MyDirectiveHandler.get('foo').updateMap();
    };
});

Кутовий підхід

Дістаньте лист із кутової книги про те, як вони поводяться

<form name="my_form"></form>

за допомогою $ parse та реєстрації контролера в $parentобласті застосування. Ця методика не працює на окремих $scopeдирективах.

angular.module('myModule').directive('myDirective', function($parse) {
    var directive = {
        link: link,
        controller: controller,
        scope: true
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        $parse($attrs.name).assign($scope.$parent, this);

        this.updateMap = function() {
            //some code
        };
    }
})

Доступ до нього всередині контролера за допомогою $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});

"Підхід Angular" виглядає чудово! Є друкарська помилка: $scope.fooмає бути$scope.my_form
Даніель Д

Так, це було б, $scope.fooоскільки наш шаблон є, <div my-directive name="foo"></div>а nameзначення атрибута - "foo". <form- лише приклад однієї з директив кутових, яка використовує цю техніку
Мудассір Алі

10

Трохи пізно, але це рішення з ізольованою сферою дії та "подіями" викликати функцію в директиві. Це рішення натхнене цим повідомленням SO від satchmorun і додає модуль та API.

//Create module
var MapModule = angular.module('MapModule', []);

//Load dependency dynamically
angular.module('app').requires.push('MapModule');

Створіть API для спілкування з директивою. AddUpdateEvent додає подію до масиву подій, а updateMap викликає кожну функцію події.

MapModule.factory('MapApi', function () {
    return {
        events: [],

        addUpdateEvent: function (func) {
            this.events.push(func);
        },

        updateMap: function () {
            this.events.forEach(function (func) {
                func.call();
            });
        }
    }
});

(Можливо, вам доведеться додати функціональність для видалення події.)

У директиві встановіть посилання на MapAPI і додайте $ range.updateMap як подію, коли викликається MapApi.updateMap.

app.directive('map', function () {
    return {
        restrict: 'E', 
        scope: {}, 
        templateUrl: '....',
        controller: function ($scope, $http, $attrs, MapApi) {

            $scope.api = MapApi;

            $scope.updateMap = function () {
                //Update the map 
            };

            //Add event
            $scope.api.addUpdateEvent($scope.updateMap);

        }
    }
});

У головному контролері додайте посилання на MapApi та просто зателефонуйте MapApi.updateMap (), щоб оновити карту.

app.controller('mainController', function ($scope, MapApi) {

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}

2
Ця пропозиція потребує трохи більше роботи в реальному світі, коли у вас є кілька директив одного типу, залежно від послуги API. Ви обов'язково потрапите в ситуацію, коли вам потрібно націлювати та викликати функції лише з однієї конкретної директиви, а не з усіх. Чи хотіли б ви покращити свою відповідь рішенням цього питання?
smajl

5

Ви можете вказати атрибут DOM, який можна використовувати для дозволу директиві визначати функцію в батьківській області. Потім батьківська область може викликати цей метод, як і будь-який інший. Ось плаунер. А нижче - відповідний код.

clearfn є атрибутом елемента директиви, в який батьківський діапазон може передавати властивість області, яку потім директива може встановити на функцію, яка виконує бажану поведінку.

<!DOCTYPE html>
<html ng-app="myapp">
  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <style>
      my-box{
        display:block;
        border:solid 1px #aaa;
        min-width:50px;
        min-height:50px;
        padding:.5em;
        margin:1em;
        outline:0px;
        box-shadow:inset 0px 0px .4em #aaa;
      }
    </style>
  </head>
  <body ng-controller="mycontroller">
    <h1>Call method on directive</h1>
    <button ng-click="clear()">Clear</button>
    <my-box clearfn="clear" contentEditable=true></my-box>
    <script>
      var app = angular.module('myapp', []);
      app.controller('mycontroller', function($scope){
      });
      app.directive('myBox', function(){
        return {
          restrict: 'E',
          scope: {
            clearFn: '=clearfn'
          },
          template: '',
          link: function(scope, element, attrs){
            element.html('Hello World!');
            scope.clearFn = function(){
              element.html('');
            };
          }
        }
      });
    </script>
  </body>
</html>

Я не розумію, чому це працює ... це тому, що чіткий атрибут є в області деяким чином?
Квінн Вілсон

1
Він стає частиною сфери дії директиви, як тільки ви її оголосите (наприклад scope: { clearFn: '=clearfn' }).
Тревор

2

Просто використовуйте область. $ Parent для асоціації функції, викликаної до директивної функції

angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) {

}])
.directive('mydirective',function(){
 function link(scope, el, attr){
   //use scope.$parent to associate the function called to directive function
   scope.$parent.myfunction = function directivefunction(parameter){
     //do something
}
}
return {
        link: link,
        restrict: 'E'   
      };
});

в HTML

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>

2

Ви можете сказати ім'я методу директиві, щоб визначити, який ви хочете викликати з контролера, але без ізоляції області,

angular.module("app", [])
  .directive("palyer", [
    function() {
      return {
        restrict: "A",
        template:'<div class="player"><span ng-bind="text"></span></div>',
        link: function($scope, element, attr) {
          if (attr.toPlay) {
            $scope[attr.toPlay] = function(name) {
              $scope.text = name + " playing...";
            }
          }
        }
      };
    }
  ])
  .controller("playerController", ["$scope",
    function($scope) {
      $scope.clickPlay = function() {
        $scope.play('AR Song');
      };
    }
  ]);
.player{
  border:1px solid;
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="playerController">
    <p>Click play button to play
      <p>
        <p palyer="" to-play="play"></p>
        <button ng-click="clickPlay()">Play</button>

  </div>
</div>


1

ТЕСТОВАНИЙ Сподіваюся, що це комусь допоможе.

Мій простий підхід (Помітьте теги як свій вихідний код)

<html>
<div ng-click="myfuncion"> 
<my-dir callfunction="myfunction">
</html>

<directive "my-dir">
callfunction:"=callfunction"
link : function(scope,element,attr) {
scope.callfunction = function() {
 /// your code
}
}
</directive>

0

Можливо, це не найкращий вибір, але ви можете зробити angular.element("#element").isolateScope()або $("#element").isolateScope()отримати доступ до області та / або контролера вашої директиви.


0

Як отримати контролер директиви у контролері сторінки:

  1. написати спеціальну директиву, щоб отримати посилання на контролер директиви від елемента DOM:

    angular.module('myApp')
        .directive('controller', controller);
    
    controller.$inject = ['$parse'];
    
    function controller($parse) {
        var directive = {
            restrict: 'A',
            link: linkFunction
        };
        return directive;
    
        function linkFunction(scope, el, attrs) {
            var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
            var directiveController = el.controller(directiveName);
    
            var model = $parse(attrs.controller);
            model.assign(scope, directiveController);
        }
    }
  2. використовувати його в html-контролері сторінки:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. Використовуйте контролер директив у контролері сторінки:

    vm.myDirectiveController.callSomeMethod();

Примітка: дане рішення працює лише для контролерів директив елементів (ім'я тега використовується для отримання імені шуканої директиви).


0

Нижче рішення буде корисним, коли у вас є контролери (батьківські та директивні (ізольовані)) у форматі "контролер як"

хтось може вважати це корисним,

директива:

var directive = {
        link: link,
        restrict: 'E',
        replace: true,
        scope: {
            clearFilters: '='
        },
        templateUrl: "/temp.html",
        bindToController: true, 
        controller: ProjectCustomAttributesController,
        controllerAs: 'vmd'
    };
    return directive;

    function link(scope, element, attrs) {
        scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
    }
}

Директор-контролер:

function DirectiveController($location, dbConnection, uiUtility) {
  vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;

function SetFitlersToDefaultValue() {
           //your logic
        }
}

html-код:

      <Test-directive clear-filters="vm.ClearFilters"></Test-directive>
    <a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a> 
//this button is from parent controller which will call directive controller function
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.