Как вызвать метод, определенный в директиве 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()с контроллера?

mcbjam
источник
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.
Rromiem
5
Это чище, чем принятый ответ, и +1 за ссылку Симпсонов, если я не ошибаюсь
Блейк Миллер
44
Именно так я и решил ту же проблему. Это работает, но выглядит как хак ... Я бы хотел, чтобы у angular было лучшее решение для этого.
Дема
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

Марк Райкок
источник
Что если директива имеет изолированную область?
Флориан Ф
Спасибо! (Возможно, было бы проще вызвать функцию, определенную в контроллере директивы, но я не уверен в этом)
Florian F
1
Это гораздо лучший способ, если вы не имеете дело с изолированной областью действия.
Мартин Франк
Этот ответ действительно отвечает на вопрос ОП. Он также использует изолированную область видимости, для того чтобы иметь изолированную область видимости, нужно всего лишь добавить scopeсвойство в объявление директивы.
Даниэль Дж
35

Хотя может показаться заманчивым выставить объект в изолированной области действия директивы для облегчения связи с ним, выполнение может привести к путанице в коде «спагетти», особенно если вам нужно связать эту связь через пару уровней (от контроллера до директивы, вложенной директиве и т. д.)

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

Эта абстракция очень хорошо работает с инфраструктурой внедрения зависимостей AngularJS, поскольку вы можете внедрить службу в любые элементы, которые должны реагировать на эти события. Если вы посмотрите на файл Angular.js, то увидите, что директивы в нем также используют службы и $ watch таким образом, они не отображают события в изолированной области видимости.

Наконец, в случае, если вам нужно обмениваться данными между директивами, которые зависят друг от друга, я бы порекомендовал разделить контроллер между этими директивами в качестве средства связи.

В Wiki для лучших практик AngularJS также упоминается следующее:

Используйте только. $ Broadcast (),. $ Emit () и. $ On () для атомарных событий События, которые актуальны во всем приложении (например, аутентификация пользователя или закрытие приложения). Если вы хотите события, относящиеся к модулям, сервисам или виджетам, вы должны рассмотреть Сервисы, Директивные контроллеры или Сторонние библиотеки

  • $ scope. $ watch () должен заменить необходимость в событиях
  • Инъекционные сервисы и методы вызова напрямую также полезны для прямого общения
  • Директивы могут напрямую общаться друг с другом через директивы-контроллеры
Всегда учусь
источник
2
Я достиг двух решений интуитивно: (1) наблюдаю за изменением переменной области =, переменная содержит имя метода и аргументы. (2) выставить строку одностороннего связывания в @качестве идентификатора темы и позволить вызываемому абоненту отправить событие по этой теме. Теперь я видел лучшие практики вики. Я думаю, что есть причина не делать это в любом случае. Но мне все еще не очень понятно, как это работает. В моем случае я создал директиву tabset, я хочу предоставить switchTab(tabIndex)метод. Не могли бы вы привести пример?
stanleyxu2005
Вы бы не выставили switchTab(tabIndex)метод, вы бы связались только с tabIndexпеременной. Ваш контроллер страницы может иметь действия, которые изменяют эту переменную. Вы связываете / передаете эту переменную в вашу вкладку Directive. Ваша директива Tab может затем отслеживать эту переменную на предмет изменений и выполнять switchTab самостоятельно. Потому что директива решает, когда и как управлять своими вкладками на основе переменной. Это не работа внешнего источника, в противном случае внешние источники требуют знания внутренней работы директивы, что плохо.
Суамер
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
        };
      }
    }
  };
});
CheapSteaks
источник
на месте, использование разоблачающего паттерна внутри директивы делает намерения намного яснее. хороший!
JSancho
12

Если честно, меня не очень убедил ни один из ответов в этой теме. Итак, вот мои решения:

Директива Обработчик (Менеджер) Подход

Этот метод не зависит от того, $scopeявляется ли директива разделяемой или изолированной

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

Угловой подход

Извлечение листа из книги Angular о том, как они справляются

<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();
    };
});
Мудассир али
источник
«Угловой подход» выглядит великолепно! Хотя есть опечатка: $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 и добавьте $ scope.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();    
    };
}
AxdorphCoder
источник
2
Это предложение потребует немного больше работы в реальном мире, когда у вас есть несколько директив одного и того же типа в зависимости от службы API. Вы обязательно попадете в ситуацию, когда вам нужно нацеливать и вызывать функции только из одной конкретной директивы, а не из всех. Хотели бы вы дополнить свой ответ решением этой проблемы?
Смайл
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>
Тревор
источник
Я не понимаю, почему это работает .. это потому, что атрибут clear находится в области видимости, как?
Куинн Уилсон
1
Он становится частью области действия директивы, как только вы ее объявляете (например scope: { clearFn: '=clearfn' }).
Тревор
2

Просто используйте scope. $ 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()получить доступ к области действия и / или контроллеру вашей директивы.

Alex198710
источник
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
Раунак Мали
источник