web-dev-qa-db-fra.com

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 de l'utilisateur. Le bouton d'action n'est pas sur la directive. 

Quel est le meilleur moyen d’appeler updateMap() depuis un contrôleur?

288
mcbjam

Si vous souhaitez utiliser des étendues isolées, vous pouvez transmettre un objet de contrôle à l'aide de la liaison bidirectionnelle = d'une variable de l'étendue 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>

356
Oliver Wienand

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 alors appeler cette fonction lorsque vous cliquez sur le bouton d'action.

<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


Selon le commentaire de @ FlorianF, si la directive utilise une portée isolée, les choses sont plus compliquées. Voici un moyen de le faire fonctionner: ajoutez un attribut set-fn à la directive map qui enregistrera la fonction de 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;
    };
}

fiddle

71
Mark Rajcok

Même s'il peut être tentant d'exposer un objet sur le champ d'application isolé d'une directive pour faciliter la communication avec lui, cela peut entraîner une confusion du code "spaghetti", en particulier si vous devez chaîner cette communication sur plusieurs niveaux (contrôleur, directive, directive imbriquée, etc.) 

Au départ, nous avions emprunté cette voie, mais après quelques recherches supplémentaires, nous avons constaté qu’il était plus logique de créer un code plus facile à gérer et à lire pour exposer les événements et les propriétés qu’une directive utilisera pour la communication via un service puis utilisant la directive ou tout autre contrôle qui devrait réagir à ces changements pour la communication.

Cette abstraction fonctionne très bien avec le cadre d'injection de dépendance d'AngularJS, car vous pouvez injecter le service dans tous les éléments devant réagir à ces événements. Si vous examinez le fichier Angular.js, vous verrez que les directives qui y sont contenues utilisent également des services et que $ watch de cette manière, elles n'exposent pas les événements situés au-delà de la portée isolée.

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

Le wiki des meilleures pratiques d’AngularJS mentionne également ceci:

Utilisez uniquement. $ Broadcast (),. $ Emit () et. $ On () pour les événements atomiques Événements pertinents à l'échelle mondiale pour l'ensemble de l'application (tels que l'authentification d'un utilisateur ou la fermeture de l'application). Si vous souhaitez des événements spécifiques à des modules, services ou widgets, vous devez envisager Services, Contrôleurs de contrôle ou Bibliothèques tierces.

  • $ scope. $ 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 via des contrôleurs de directives
34
Always Learning

En vous appuyant sur la réponse d'Oliver, vous n'aurez peut-être pas toujours besoin d'accéder aux méthodes internes d'une directive. Dans ces cas, vous ne voudrez probablement pas avoir à créer un objet vide et à ajouter un control attr à la directive juste pour l'empêcher de générer 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 les méthodes de la même manière que le modèle de module révélateur

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
CheapSteaks

Pour être honnête, aucune des réponses à ce sujet ne m'a convaincu. Alors, voici mes solutions:

Approche de gestionnaire de directives (gestionnaire)

Cette méthode ne dépend pas du fait que le $scope de la directive soit partagé ou isolé.

Un 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 en enregistrant l'instance de contrôleur dans 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 de modèle

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

Accédez à l'instance du contrôleur à l'aide de factory & 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. 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édez-y à l'intérieur du contrôleur en utilisant $scope.foo

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

Un peu tard, mais c'est une solution avec la portée isolée et des "événements" pour appeler une fonction dans la directive. Cette solution s’inspire de this SO post de - satchmorun et ajoute un module et 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();
            });
        }
    }
});

(Vous devez peut-être ajouter une fonctionnalité pour supprimer un événement.)

Dans la directive, définissez une référence à MapAPI et ajoutez $ scope.updateMap en tant qu'é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 à MapApi et appelez simplement MapApi.updateMap () pour mettre à jour la carte.

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

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

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

clearfn est un attribut de l'élément de directive dans lequel la portée parente peut transmettre une propriété de portée que la directive peut ensuite définir pour une fonction accomplissant 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
Trevor

Il suffit d'utiliser scope. $ Parent pour associer une fonction appelée à une fonction de 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
ramon prata

Vous pouvez indiquer le nom de la méthode à la directive pour définir celui que vous voulez appeler depuis le 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
Naveen raj

TEST&EACUTE; J'espère que cela aidera quelqu'un.

Mon approche simple (pensez aux balises comme votre 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
Santosh Kumar

Ce n'est peut-être pas le meilleur choix, mais vous pouvez utiliser angular.element("#element").isolateScope() ou $("#element").isolateScope() pour accéder à l'étendue et/ou au contrôleur de votre directive.

0
Alex198710

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

cela pourrait être 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;
    }
}

directive contrôleur: 

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
Raunak Mali

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

Remarque: la solution donnée ne fonctionne que pour les contrôleurs des directives d'élément (le nom de balise est utilisé pour obtenir le nom de la directive recherchée).

0
Robert J