Comment appeler une méthode définie dans une directive AngularJS?

J'ai une directive, voici le code :

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

Je voudrais appeler updateMap() sur une action utilisateur. Le bouton d'action n'est pas sur la directive.

Quelle est la meilleure façon d'appeler updateMap() à partir d'un contrôleur?

282
demandé sur Ben 2013-06-02 13:29:48

13 réponses

Si vous souhaitez utiliser des étendues isolées, vous pouvez passer un objet de contrôle en utilisant la liaison bidirectionnelle = d'une variable de la portée du contrôleur. Vous pouvez également contrôler plusieurs instances de la même directive sur une page avec le même objet de contrôle.

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
répondu Oliver Wienand 2016-05-18 07:31:05

En supposant que le bouton d'action utilise le même contrôleur $scope que la directive, définissez simplement la fonction updateMap sur $scope à l'intérieur de la fonction link. Votre contrôleur peut ensuite appeler cette fonction lorsque le bouton est cliqué.

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

violon


Selon le commentaire de @ FlorianF, si la directive utilise une portée isolée, les choses sont plus compliquées. Voici une façon de le faire fonctionner: ajoutez un attribut set-fn à la directive map qui enregistrez la fonction directive avec le contrôleur:

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

violon

70
répondu Mark Rajcok 2013-06-05 14:39:20

Bien qu'il puisse être tentant d'exposer un objet sur la portée isolée d'une directive pour faciliter la communication avec elle, cela peut conduire à un code "spaghetti" confus, surtout si vous devez enchaîner cette communication à travers quelques niveaux (contrôleur, directive, directive imbriquée, etc.)

À l'origine, Nous sommes allés dans cette voie, mais après quelques recherches supplémentaires, il a été constaté que cela avait plus de sens et a abouti à un code plus maintenable et plus lisible pour exposer les événements et propriétés qu'une directive utilisera pour la communication via un service, puis en utilisant $watch sur les propriétés de ce service dans la directive ou tout autre contrôle qui devrait réagir à ces changements pour la communication.

Cette abstraction fonctionne très bien avec le framework d'injection de dépendance D'AngularJS car vous pouvez injecter le service dans tous les éléments qui doivent réagir à ces événements. Si vous regardez l'angle.fichier js, vous verrez que les directives utilisent également les services et $ watch in de cette manière, ils n'exposent pas les événements sur la portée isolée.

Enfin, dans le cas où vous devez communiquer entre des directives qui dépendent les unes des autres, je recommanderais de partager un contrôleur entre ces directives comme moyen de communication.

Le Wiki D'AngularJS pour les meilleures pratiques mentionne également ceci:

Utiliser uniquement .$radiodiffusion(), .$emit() et .$sur() pour les événements atomiques Les événements qui sont pertinents à l'échelle mondiale dans l'ensemble de l'application (tels que l'authentification de l'utilisateur ou la fermeture de l'application). Si vous voulez des événements spécifiques aux modules, services ou widgets, vous devriez considérer les services, les contrôleurs de Directive ou les bibliothèques tierces

  • $ portée.$watch() devrait remplacer le besoin d'événements
  • L'injection directe de services et de méthodes d'appel est également utile pour la communication directe
  • les Directives peuvent communiquer directement les unes avec les autres par l'intermédiaire des contrôleurs de directive
34
répondu Always Learning 2014-05-30 17:19:19

En S'appuyant sur la réponse D'Oliver - vous n'avez peut-être pas toujours besoin d'accéder aux méthodes internes d'une directive, et dans ces cas, vous ne voulez probablement pas avoir à créer un objet vide et Ajouter un control attr à la directive juste pour l'empêcher de lancer une erreur (cannot set property 'takeTablet' of undefined).

Vous pouvez également utiliser la méthode à d'autres endroits de la directive.

J'ajouterais une vérification pour m'assurer que scope.control existe, et définirais des méthodes de la même manière que le module révélateur motif

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
répondu CheapSteaks 2014-03-16 01:13:31

Un peu en retard, mais c'est une solution avec la portée isolée et les "événements" pour appeler une fonction dans la directive. Cette solution est inspirée par ce DONC, après les par satchmorun et ajoute un module et d'une API.

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

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

Créez une API pour communiquer avec la directive. AddUpdateEvent ajoute un événement au tableau d'événements et updateMap appelle chaque fonction d'événement.

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

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

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

(peut-être que vous devez ajouter des fonctionnalités pour supprimer l'événement.)

Dans la directive référence au MapAPI et ajouter $ scope .updateMap comme un événement lorsque MapApi.updateMap est appelé.

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

        }
    }
});

Dans le contrôleur" principal " ajoutez une référence au MapApi et appelez simplement MapApi.updateMap() pour mettre à jour la carte.

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

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}
10
répondu AxdorphCoder 2018-06-17 07:33:02

Pour être honnête, je n'étais pas vraiment convaincu par l'une des réponses de ce fil. Alors, voici mes solutions:

Approche Du Gestionnaire De Directive(Gestionnaire)

Cette méthode est agnostique de savoir si la directive $scope est partagée ou isolé

A factory pour enregistrer les instances de directive

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

Le code de directive, je mets généralement toute la logique qui ne traite pas de DOM dans le contrôleur de directive. Et l'enregistrement de l'instance du contrôleur à l'intérieur notre gestionnaire

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

Code Modèle

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

Accédez à l'instance du contrôleur en utilisant factory et exécutez les méthodes exposées publiquement

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

Approche angulaire

Prendre une feuille du livre d'angular sur la façon dont ils traitent

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

En utilisant $parse et en enregistrant le contrôleur sur $parent scope. Cette technique ne fonctionne pas sur les directives $scope isolées.

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

Accéder à l'intérieur du contrôleur en utilisant $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});
9
répondu Mudassir Ali 2016-03-23 18:33:39

Vous pouvez spécifier un attribut DOM qui peut être utilisé pour permettre à la directive de définir une fonction sur la portée parent. La portée parent peut alors appeler cette méthode comme toute autre. Voici Un plongeur. Et ci-dessous est le code pertinent.

clearfn est un attribut sur l'élément directive dans lequel la portée parent peut passer une propriété de portée que la directive peut ensuite définir sur une fonction qui accomplit le comportement souhaité.

<!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
répondu Trevor 2014-04-25 17:18:03

Il suffit d'utiliser la portée.$ parent à associer la fonction appelée à la fonction directive

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
répondu ramon prata 2016-01-29 18:29:28

Vous pouvez indiquer le nom de la méthode à la directive pour définir ce que vous voulez appeler à partir du contrôleur mais sans isoler la portée,

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
répondu Naveen raj 2016-02-16 11:49:17

Testé Espérons que cela aide quelqu'un.

Mon approche simple (pensez aux balises comme code d'origine)

<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
répondu Santosh Kumar 2018-02-06 10:33:56

Peut-être que ce n'est pas le meilleur choix, mais vous pouvez faire angular.element("#element").isolateScope() ou $("#element").isolateScope() pour accéder à la portée et/ou au contrôleur de votre directive.

0
répondu Alex198710 2016-02-01 14:08:31

Comment obtenir le contrôleur d'une directive dans un contrôleur de page:

  1. Écrivez une directive personnalisée pour obtenir la référence au contrôleur de directive à partir de L'élément 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. Utilisez-le dans le code html du contrôleur de page:

    <my-directive controller="vm.myDirectiveController"></my-directive>
    
  3. Utilisez le contrôleur de directive dans le contrôleur de page:

    vm.myDirectiveController.callSomeMethod();
    

Note: la solution donnée ne fonctionne que pour les contrôleurs des directives element (le nom de la balise est utilisé pour obtenir le nom du directive).

0
répondu Robert J 2016-11-21 10:32:19

La solution ci-dessous sera utile lorsque vous avez des contrôleurs (parent et directive (isolés)) au format' controller As '

, Quelqu'un pourrait trouver cela utile,

Directive:

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

Contrôleur De Directive:

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

function SetFitlersToDefaultValue() {
           //your logic
        }
}

Code 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
répondu Raunak Mali 2017-04-28 10:20:29