web-dev-qa-db-fra.com

Comment exposer le comportement d'une directive avec une portée isolée?

Comment puis-je exposer une méthode à partir d'une directive? Je sais que je devrais utiliser des attributs pour les données, mais je veux vraiment exposer behavior, pas data. Quelque chose que le contrôleur parent peut appeler.

Disons que mon DOM ressemble à:

<div ng-app="main">
    <div ng-controller="MyCtrl">
        <button ng-click="call()" >Call</button>
        <div id="container" my-directive> </div>
    </div>
</div>

JavaScript:

angular.module("main", []).controller("MyCtrl", function($scope) {
    $scope.call = function() {
        $scope.myfn();
    };
}).directive("myDirective", function() {
    return {
        // scope: {},
        controller: function($scope) {
            $scope.myfn = function() {
                console.log("myfn called");
            }
        }
    };
});

jsFiddle: http://jsfiddle.net/5gDjQ/7/

Si la scope est commentée (c’est-à-dire que la directive n’a pas de portée isolée), cela fonctionne parfaitement. Lorsque j'appuie sur le bouton, myfn est appelé et se connecte à la console.

Dès que je dégage le commentaire scope, cela ne fonctionne pas. myfn est défini sur la portée de l'enfant et n'est pas facilement accessible au parent.

Dans mon cas, je pense que polluer le périmètre parent est une mauvaise idée et j'aimerais vraiment l'éviter.

Alors, comment puis-je exposer une fonction de directive au contrôleur parent? Ou: Comment puis-je invoquer une méthode sur une directive à partir du contrôleur parent?

34
Konrad Garus

Vous pouvez le faire avec une étendue isolée en définissant une variable dans l'étendue liée dans les deux sens au contrôleur (à l'aide de '='). Dans votre directive, vous pouvez ensuite affecter la fonction à cette variable. Angular utilisera la liaison pour rechercher la variable correspondante dans votre contrôleur. Cette variable indiquera une fonction que votre contrôleur peut appeler.

http://jsfiddle.net/GWCCr/

html: Notez le nouvel attribut:

<div ng-app="main">
    <div ng-controller="MyCtrl">
        <button ng-click="call()" >Call</button>
        <div id="container" my-directive my-fn="fnInCtrl"> </div>
    </div>
</div>

js:

angular.module("main", []).controller("MyCtrl", function($scope) {
    $scope.call = function() {
        $scope.fnInCtrl();
    };
}).directive("myDirective", function() {
    return {
        scope: {
            myFn: '='
        },
        controller: function($scope) {
            $scope.myFn = function() {
                console.log("myfn called");
            }
        }
    };
});
26
Roy Truelove

Plutôt que d'essayer de comprendre comment appeler une fonction cachée dans une directive, je pense que vous devriez vous demander: pourquoi je veux appeler une fonction définie dans une directive?

Une des raisons pour laquelle je peux penser est: pour déclencher un comportement de la directive qui pourrait également être déclenché par l'utilisateur de l'application à partir de la directive.

Si tel est le cas, la chose la plus évidente à faire est de diffuser un événement sur un périmètre contenant la directive qui devrait y réagir. Ensuite, la directive écouterait cet événement et déclencherait sa fonction par elle-même. 

Cela présente des avantages supplémentaires:

  • vous pouvez envoyer des paramètres dans l'objet de données d'événement si vous souhaitez
  • vous pouvez déclencher une réaction dans plusieurs directives (par exemple, si elles forment une collection)
  • vous pouvez communiquer avec une directive qui se trouve profondément dans la hiérarchie de la portée (par exemple, si la directive se trouve dans une autre directive qui se trouve dans d'autres directives, etc.) sans passer un rappel de fonction à chaque directive imbriquée
  • cela ne rompt pas l'isolement de la directive

Exemple

Essayons de donner un exemple très simple: supposons que nous ayons un widget qui affiche une citation inspirante aléatoire téléchargée de quelque part. Il a également un bouton pour changer la citation à une autre.

Voici le modèle de la directive:

<p>{{ quote }}</p>
<button ng-click="refreshQuote()"></button>

Et voici le code de la directive:

app.directive("randomQuote", function () {
  return {
    restrict: "E",
    scope: {},
    link: function (scope) {
      scope.refreshQuote = function () {
        scope.quote = ... // some complicated code here
      };
      scope.refreshQuote();
    }
  };
});

Notez que la directive est entièrement autonome: elle a une portée isolée et effectue l'extraction de devis par elle-même.

Supposons que nous souhaitions également pouvoir actualiser la citation à partir du contrôleur. Cela pourrait être aussi simple que d'appeler cela dans le code du contrôleur:

$scope.$broadcast("refresh-random-quote");

Pour ajouter le gestionnaire d'événements, nous devons ajouter ce code à la fonction link de la directive:

scope.$on("refresh-random-quote", function () {
  scope.refreshQuote();
});

De cette façon, nous avons créé un canal de communication unidirectionnel du contrôleur à la directive qui ne rompt pas l'isolement de la directive et fonctionne également si la directive est imbriquée au plus profond de la hiérarchie du code qui diffuse l'événement. .

16
DzinX

Comment puis-je exposer une fonction de directive au contrôleur parent?
Ou: Comment puis-je invoquer une méthode sur une directive à partir du contrôleur parent?

Eh bien, je ne pense pas que vous devriez essayer de faire cela (c'est-à-dire coupler le comportement du contrôleur à une directive), mais si vous devez ... voici une façon de le faire: passer une fonction de contrôleur à votre directive, directive peut appeler pour notifier au contrôleur la fonction de directive:

<div id="container" my-directive cb="setDirectiveFn(fn)"></div>

directive("myDirective", function() {
    return {
       scope: { cb: '&' },
        controller: function($scope) {
            $scope.myfn = function() {
                console.log("myfn called");
            }
            $scope.cb({fn: $scope.myfn});
        }
    };
});

Fiddle

7
Mark Rajcok

Pour contribuer, @georgeawg m'a donné une solution intéressante en utilisant Service pour faire le travail. De cette façon, vous pouvez gérer plusieurs directives sur la même page.

<html ng-app="myApp">
<head>
  <script src="https://opensource.keycdn.com/angularjs/1.6.5/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
  <h1>^v1.6.0 ($postLink hook required)</h1>
  <my-directive name="sample1" number="number1"></my-directive>
  <my-directive name="sample2" number="number2"></my-directive>
</body>
<script>
  angular.module('myApp', [])
    .controller('mainCtrl', ['$scope', 'myDirectiveFactory', function ($scope, myDirectiveFactory) {
      $scope.number1 = 10
      $scope.number2 = 0
      this.$postLink = function () {
        myDirectiveFactory.get('sample2')
          .increment()
          .increment()
          .increment()
          .increment()
        myDirectiveFactory.get('sample1')
          .increment()
          .increment()
        myDirectiveFactory.get('sample2')
        .decrement()
      }
    }])
    .factory('myDirectiveFactory', function () {
      var instance = {}
      return {
        get: function (name) {
          return instance[name]
        },
        register: function (name, value) {
          return instance[name] = value
        },
        destroy: function (name) {
          delete instance[name]
        }
      }
    })
    .controller('myDirectiveCtrl', ['$scope', 'myDirectiveFactory', function ($scope, myDirectiveFactory) {
      $scope.name = $scope.name || 'myDirective'
      $scope.$on('$destroy', function () {
        myDirectiveFactory.destroy($scope.name)
      })
      var service = {
        increment: function () {
          $scope.number++
          return this
        },
        decrement: function () {
          $scope.number--
          return this
        }
      }
      myDirectiveFactory.register($scope.name, service)
    }])
    .directive('myDirective', [function () {
      return {
        controller: 'myDirectiveCtrl',
        restrict: 'E',
        scope: {
          number: '<',
          name: '@?'
        },
        template: '<p> {{ number }} </p>'
      }
    }])
</script>
</html>
1
saulsluz

La sortie de AngularJS V1.7.1* introduit la nouvelle directive ng-ref .

L'attribut ng-ref indique à AngularJS de publier le contrôleur d'un composant sur l'étendue actuelle. Cela est utile pour qu'un composant tel qu'un lecteur audio expose son API aux composants frères. Ses commandes de lecture et d'arrêt sont facilement accessibles.

Pour plus d'informations, voir

0
georgeawg