¿Cómo llamar a un método definido en una directiva AngularJS?


Tengo una directiva, aquí está el código :

.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);  
        }
    }
})

Me gustaría llamar a updateMap() en una acción de usuario. El botón de acción no está en la directiva.

¿Cuál es la mejor manera de llamar a updateMap() desde un controlador?

Author: Ben, 2013-06-02

13 answers

Si desea utilizar ámbitos aislados, puede pasar un objeto de control mediante el enlace bidireccional = de una variable desde el ámbito del controlador. También puede controlar varias instancias de la misma directiva en una página con el mismo objeto de control.

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>
 350
Author: Oliver Wienand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-18 07:31:05

Asumiendo que el botón de acción usa el mismo controlador $scope que la directiva, simplemente defina function updateMap en $scope dentro de la función link. El controlador puede llamar a esa función cuando se hace clic en el botón de acción.

<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


Según el comentario de @FlorianF, si la directiva utiliza un ámbito de aplicación aislado, las cosas son más complicadas. Aquí hay una manera de hacer que funcione: agregue un atributo set-fn a la directiva map que registrar la función directiva con el controlador:

<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

 70
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-05 14:39:20

Aunque podría ser tentador exponer un objeto en el ámbito aislado de una directiva para facilitar la comunicación con ella, hacerlo puede llevar a confundir el código "espagueti", especialmente si necesita encadenar esta comunicación a través de un par de niveles (controlador, a directiva, a directiva anidada, etc.).)

Originalmente fuimos por este camino, pero después de un poco más de investigación encontró que tenía más sentido y resultó en un código más mantenible y legible para exponer eventos y propiedades que una directiva utilizará para la comunicación a través de un servicio y luego utilizará watch watch en las propiedades de ese servicio en la directiva o cualquier otro control que necesite reaccionar a esos cambios para la comunicación.

Esta abstracción funciona muy bien con el marco de inyección de dependencias de AngularJS, ya que puede inyectar el servicio en cualquier elemento que necesite reaccionar a esos eventos. Si nos fijamos en el Angular.archivo js, verá que las directivas allí también utilizan servicios y watch watch in de esta manera, no exponen eventos sobre el ámbito aislado.

Por último, en el caso de que necesite comunicarse entre directivas que dependen unas de otras, le recomendaría compartir un controlador entre esas directivas como medio de comunicación.

La Wiki de AngularJS para Mejores Prácticas también menciona esto:

Utilizar únicamente .$transmitir(), .emit emit () y .on on () para eventos atómicos Eventos que son relevantes a nivel mundial en toda la aplicación (como autenticación de un usuario o cierre de la aplicación). Si desea eventos específicos para módulos, servicios o widgets, debe considerar Servicios, Controladores de Directiva o Libs de terceros

  • scope alcance.watch watch () debería reemplazar la necesidad de eventos
  • Inyectar servicios y métodos de llamada directamente también es útil para la comunicación directa
  • Las directivas pueden comunicarse directamente entre sí a través de controladores-directivas{[17]]}
 34
Author: Always Learning,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-30 17:19:19

Basándose en la respuesta de Oliver - es posible que no siempre necesite acceder a los métodos internos de una directiva, y en esos casos probablemente no desee tener que crear un objeto en blanco y agregar un control attr a la directiva solo para evitar que lance un error (cannot set property 'takeTablet' of undefined).

Es posible que también desee usar el método en otros lugares dentro de la directiva.

Añadiría una comprobación para asegurarme de que scope.control existe, y establecería los métodos en ella de manera similar al módulo de revelación patrón

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
        };
      }
    }
  };
});
 15
Author: CheapSteaks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-16 01:13:31

Un poco tarde, pero esta es una solución con el ámbito aislado y "eventos" para llamar a una función en la directiva. Esta solución está inspirada en this SO post by satchmorun y agrega un módulo y una API.

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

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

Cree una API para comunicarse con la directiva. El addUpdateEvent agrega un evento a la matriz de eventos y updateMap llama a cada función de evento.

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

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

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

(Tal vez tenga que agregar funcionalidad para eliminar el evento.)

En el conjunto de la directiva a referencia al MapAPI y añadir scope scope.updateMap como un evento cuando MapApi.Se llama 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);

        }
    }
});

En el controlador "principal" agregue una referencia al MapApi y simplemente llame a MapApi.updateMap() para actualizar el mapa.

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

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}
 10
Author: AxdorphCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-17 07:33:02

Para ser honesto, no estaba realmente convencido con ninguna de las respuestas en este hilo. Por lo tanto, aquí están mis soluciones:

Enfoque del Manejador de Directivas(Gestor)

Este método es independiente de si la directiva $scope es compartida o aislado

A factory para registrar las instancias de directiva

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;
    }
});

El código de directiva, normalmente pongo toda la lógica que no trata con DOM dentro del controlador de directiva. Y registrar la instancia del controlador dentro nuestro manejador

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);
        });
    }
})

Código de plantilla

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

Acceda a la instancia del controlador usando factory y ejecute los métodos expuestos públicamente

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

Aproximación angular

Tomando una hoja del libro de angular sobre cómo tratan con{[14]]}

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

Usando par parse y registrando el controlador en el ámbito $parent. Esta técnica no funciona en directivas aisladas $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
        };
    }
})

Accede a él dentro del controlador usando $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});
 9
Author: Mudassir Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-23 18:33:39

Puede especificar un atributo DOM que se puede usar para permitir que la directiva defina una función en el ámbito padre. El ámbito padre puede llamar a este método como cualquier otro. Aquí está un émbolo. Y abajo está el código relevante.

clearfn es un atributo en el elemento de directiva al que el ámbito padre puede pasar una propiedad de ámbito que la directiva puede establecer como una función que logre el comportamiento deseado.

<!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>
 5
Author: Trevor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-25 17:18:03

Simplemente use scope.function parent to associate function called to directive function

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'   
      };
});

En HTML

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>
 2
Author: ramon prata,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-29 18:29:28

Puede indicar el nombre del método a la directiva para definir el que desea llamar desde el controlador, pero sin ámbito aislado,

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>
 2
Author: Naveen raj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-16 11:49:17

PROBADO Espero que esto ayude a alguien.

Mi enfoque simple (Piense en las etiquetas como su código original)

<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>
 1
Author: Santosh Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-06 10:33:56

Tal vez esta no sea la mejor opción, pero puede hacer angular.element("#element").isolateScope() o $("#element").isolateScope() para acceder al ámbito y/o al controlador de su directiva.

 0
Author: Alex198710,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-01 14:08:31

Cómo obtener el controlador de una directiva en un controlador de página:

  1. Escriba una directiva personalizada para obtener la referencia al controlador de directiva del elemento 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. Utilícelo en el html del controlador de página:

    <my-directive controller="vm.myDirectiveController"></my-directive>
    
  3. Utilice el controlador de directiva en el controlador de página:

    vm.myDirectiveController.callSomeMethod();
    

Nota: la solución dada solo funciona para los controladores de las directivas de elemento (tag name se usa para obtener el nombre de la directriz).

 0
Author: Robert J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-21 10:32:19

La siguiente solución será útil cuando tenga controladores (tanto padre como directiva (aislados)) en formato 'controlador como'

Alguien podría encontrar esto útil,

Directiva:

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;
    }
}

Controlador de directiva:

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

function SetFitlersToDefaultValue() {
           //your logic
        }
}

Código 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
 0
Author: Raunak Mali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-28 10:20:29