la directive angularjs appelle la fonction spécifiée dans l'attribut et lui passe un argument

103

Je veux créer une directive liée à un attribut. L'attribut spécifie la fonction qui doit être appelée sur la portée. Mais je veux aussi passer un argument à la fonction qui est déterminée dans la fonction de lien.

<div my-method='theMethodToBeCalled'></div>

Dans la fonction de lien, je me lie à un événement jQuery, qui transmet un argument que je dois transmettre à la fonction:

app.directive("myMethod",function($parse) {
  restrict:'A',
  link:function(scope,element,attrs) {
     var expressionHandler = $parse(attrs.myMethod);
     $(element).on('theEvent',function( e, rowid ) {
        id = // some function called to determine id based on rowid
        scope.$apply(function() {expressionHandler(id);});
     }
  }
}

app.controller("myController",function($scope) {
   $scope.theMethodToBeCalled = function(id) { alert(id); };
}

Sans passer l'identifiant, je peux le faire fonctionner, mais dès que j'essaye de passer un argument, la fonction n'est plus appelée

rekna
la source
comment accéder à la propriété de l'objet via isoler la portée sans liaison bidirectionnelle? je pense que c'est utile. utilisez la portée isolée et appelez la portée parent via $ scope. $ parent
JJbang

Réponses:

98

La solution de Marko fonctionne bien .

Pour contraster avec la méthode Angular recommandée (comme le montre le plunkr de treeface), il faut utiliser une expression de rappel qui ne nécessite pas de définir expressionHandler. Dans l'exemple de Marko, changez:

Dans le modèle

<div my-method="theMethodToBeCalled(myParam)"></div>

Dans la fonction de liaison directive

$(element).click(function( e, rowid ) {
  scope.method({myParam: id});
});

Cela présente un inconvénient par rapport à la solution de marko - lors du premier chargement, la fonctionMethodToBeCalled sera appelée avec myParam === undefined.

Un exemple de travail peut être trouvé à @treeface Plunker

Johans
la source
En effet, ce modèle de fourniture du nom de la fonction avec un paramètre dans l'attribut personnalisé semble être le moyen le plus simple et le plus robuste pour définir des "fonctions de rappel" dans les directives ... thx !!
rekna
3
Il est très déroutant d'appeler "setProduct" 2 choses différentes - attribut et fonction de portée, il est vraiment difficile de comprendre lequel est où.
Dmitri Zaitsev
Ce qui est déroutant, c'est pourquoi la bonne réponse utilise la portée d'isolat, mais la question ne le fait pas. Ou est-ce que je manque quelque chose?
j_walker_dev
D'après mes tests utilisant ce code en angulaire 1.2.28, cela fonctionne bien. Mes tests n'appellent pas theMethodToBeCalled avec undefined lors du premier chargement. L'exemple de plunker non plus. Cela ne semble pas être un problème. En d'autres termes, cela semble être la bonne approche lorsque vous souhaitez transmettre des paramètres au lien de directive (dans une portée d'isolat). Je recommande de mettre à jour le commentaire concernant l'inconvénient et myParam === undefined.
jazeee
Cela "Cela a un inconvénient par rapport à la solution de marko - lors du premier chargement, la fonctionMethodToBeCalled sera appelée avec myParam === undefined" ne se produit pas dans mon cas .... je suppose qu'il y a un contexte implicite ici
Victor
95

Juste pour ajouter quelques informations aux autres réponses - l'utilisation &est un bon moyen si vous avez besoin d'une portée isolée.

Le principal inconvénient de la solution de marko est qu'elle vous oblige à créer une portée isolée sur un élément, mais vous ne pouvez en avoir qu'une seule sur un élément (sinon vous rencontrerez une erreur angulaire: plusieurs directives [directive1, directive2] demandant pour portée isolée )

Cela signifie que vous :

  • ne peut pas l'utiliser sur un élément le chapeau a une portée isolée
  • impossible d'utiliser deux directives avec cette solution sur le même élément

Étant donné que la question initiale utilise une directive avec les restrict:'A'deux situations, cela peut se produire assez souvent dans des applications plus importantes, et utiliser ici un champ d'application isolé n'est pas une bonne pratique et également inutile. En fait, rekna avait une bonne intuition dans ce cas, et l'avait presque fait fonctionner, la seule chose qu'il faisait de mal était d'appeler la fonction $ parsed de manière incorrecte (voir ce qu'elle renvoie ici: https://docs.angularjs.org/api/ ng / service / $ parse ).

TL, DR; Code de question fixe

<div my-method='theMethodToBeCalled(id)'></div>

et le code

app.directive("myMethod",function($parse) {
  restrict:'A',
  link:function(scope,element,attrs) {
     // here you can parse any attribute (so this could as well be,
     // myDirectiveCallback or multiple ones if you need them )
     var expressionHandler = $parse(attrs.myMethod);
     $(element).on('theEvent',function( e, rowid ) {
        calculatedId = // some function called to determine id based on rowid

        // HERE: call the parsed function correctly (with scope AND params object)
        expressionHandler(scope, {id:calculatedId});
     }
  }
}

app.controller("myController",function($scope) {
   $scope.theMethodToBeCalled = function(id) { alert(id); };
}
Robert Bak
la source
11
+1 En effet - pas besoin d'isoler la lunette - beaucoup plus propre!
Dmitri Zaitsev
que se passe-t-il si l'attribut contient juste le nom de la méthode, comme <div my-method = 'theMethodToBeCalled'> </div> ou une fonction en ligne comme <div my-method = 'alert ("hi");'> </div>
Jonathan.
1
Si vous rencontrez des problèmes avec l'une de ces approches, gardez à l'esprit que la méthode appelée doit être disponible dans la portée que vous passez à la fonction renvoyée $parse.
ragamufin
Cette réponse est exactement ce que je recherchais +1
grimmdude
quel est l'événement"? Comment puis-je exécuter la fonction sur un div enfant?
Shlomo du
88

Ne sachant pas exactement ce que vous voulez faire ... mais voici toujours une solution possible.

Créez une étendue avec une propriété «&» dans la portée locale. Il "fournit un moyen d'exécuter une expression dans le contexte de la portée parent" (voir la documentation de la directive pour plus de détails).

J'ai également remarqué que vous avez utilisé une fonction de liaison abrégée et que vous y avez ajouté des attributs d'objet. Vous ne pouvez pas faire ça. Il est plus clair (à mon humble avis) de simplement renvoyer l'objet de définition de directive. Voir mon code ci-dessous.

Voici un exemple de code et un violon .

<div ng-app="myApp">
<div ng-controller="myController">
    <div my-method='theMethodToBeCalled'>Click me</div>
</div>
</div>

<script>

   var app = angular.module('myApp',[]);

   app.directive("myMethod",function($parse) {
       var directiveDefinitionObject = {
         restrict: 'A',
         scope: { method:'&myMethod' },
         link: function(scope,element,attrs) {
            var expressionHandler = scope.method();
            var id = "123";

            $(element).click(function( e, rowid ) {
               expressionHandler(id);
            });
         }
       };
       return directiveDefinitionObject;
   });

   app.controller("myController",function($scope) {
      $scope.theMethodToBeCalled = function(id) { 
          alert(id); 
      };
   });

</script>
Marko
la source
La fonction est appelée, lorsque je place $ scope.id = id dans theMethodToBeCalled, la vue n'est pas mise à jour. J'ai probablement besoin d'encapsuler expressionHandler (id) dans scope.apply.
rekna
2
Génial, cela m'a aidé à trouver la solution à mon problème. Il est à noter que vous ne devez le faire au niveau supérieur que si vous effectuez un nid plus profond de directives. Considérez ceci: plnkr.co/edit/s3y67iGL12F2hDER2RNl?p=preview où je passe la méthode à travers deux directives.
treeface
3
Et voici une deuxième façon (peut-être plus angulaire) de le faire: plnkr.co/edit/r9CV9Y1RWRuse4RwFPka?p=preview
treeface
1
Comment puis-je le faire sans isolement de la portée?
Evan Lévesque
3
+1 parce que cette solution m'a appris que je devais appeler scope.method (); d'abord pour obtenir la référence réelle à la méthode.
Sal
6

Vous pouvez créer une directive qui exécute un appel de fonction avec des paramètres en utilisant attrName: "&"pour référencer l'expression dans la portée externe.

Nous voulons remplacer la ng-clickdirective par ng-click-x:

<button ng-click-x="add(a,b)">Add</button>

Si nous avions cette portée:

$scope.a = 2;
$scope.b = 2;

$scope.add = function (a, b) {
  $scope.result = parseFloat(a) + parseFloat(b);
}

Nous pourrions écrire notre directive comme ceci:

angular.module("ng-click-x", [])

.directive('ngClickX', [function () {

  return {

    scope: {

      // Reference the outer scope
      fn: "&ngClickX",

    },

    restrict: "A",

    link: function(scope, elem) {

      function callFn () {
        scope.$apply(scope.fn());
      }

      elem[0].addEventListener('click', callFn);
    }
  };
}]);

Voici une démo en direct: http://plnkr.co/edit/4QOGLD?p=info

f1lt3r
la source
2

Voici ce qui a fonctionné pour moi.

HTML utilisant la directive

 <tr orderitemdirective remove="vm.removeOrderItem(orderItem)" order-item="orderitem"></tr>

Html de la directive: orderitem.directive.html

<md-button type="submit" ng-click="remove({orderItem:orderItem})">
       (...)
</md-button>

Champ d'application de la directive:

scope: {
    orderItem: '=',
    remove: "&",
tymtam
la source
0

Ma solution:

  1. sur le polymère déclencher un événement (par exemple. complete )
  2. définir une directive liant l'événement à la fonction de contrôle

Directif

/*global define */
define(['angular', './my-module'], function(angular, directives) {
    'use strict';
    directives.directive('polimerBinding', ['$compile', function($compile) {

            return {
                 restrict: 'A',
                scope: { 
                    method:'&polimerBinding'
                },
                link : function(scope, element, attrs) {
                    var el = element[0];
                    var expressionHandler = scope.method();
                    var siemEvent = attrs['polimerEvent'];
                    if (!siemEvent) {
                        siemEvent = 'complete';
                    }
                    el.addEventListener(siemEvent, function (e, options) {
                        expressionHandler(e.detail);
                    })
                }
            };
        }]);
});

Composant polymère

<dom-module id="search">

<template>
<h3>Search</h3>
<div class="input-group">

    <textarea placeholder="search by expression (eg. temperature>100)"
        rows="10" cols="100" value="{{text::input}}"></textarea>
    <p>
        <button id="button" class="btn input-group__addon">Search</button>
    </p>
</div>
</template>

 <script>
  Polymer({
    is: 'search',
            properties: {
      text: {
        type: String,
        notify: true
      },

    },
    regularSearch: function(e) {
      console.log(this.range);
      this.fire('complete', {'text': this.text});
    },
    listeners: {
        'button.click': 'regularSearch',
    }
  });
</script>

</dom-module>

Page

 <search id="search" polimer-binding="searchData"
 siem-event="complete" range="{{range}}"></siem-search>

searchData est la fonction de contrôle

$scope.searchData = function(searchObject) {
                    alert('searchData '+ searchObject.text + ' ' + searchObject.range);

}
vénérien
la source
-2

Cela devrait fonctionner.

<div my-method='theMethodToBeCalled'></div>

app.directive("myMethod",function($parse) {
  restrict:'A',
  scope: {theMethodToBeCalled: "="}
  link:function(scope,element,attrs) {
     $(element).on('theEvent',function( e, rowid ) {
        id = // some function called to determine id based on rowid
        scope.theMethodToBeCalled(id);
     }
  }
}

app.controller("myController",function($scope) {
   $scope.theMethodToBeCalled = function(id) { alert(id); };
}
Vladimir Prudnikov
la source