La directiva angularjs llama a la función especificada en el atributo y le pasa un argumento

102

Quiero crear una directiva que se vincule a un atributo. El atributo especifica la función que debe llamarse en el ámbito. Pero también quiero pasar un argumento a la función que se determina dentro de la función de enlace.

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

En la función de enlace, me vinculo a un evento jQuery, que pasa un argumento que necesito pasar a la función:

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

Sin pasar la identificación, puedo hacer que funcione, pero tan pronto como intento pasar un argumento, la función ya no se llama

rekna
fuente
¿Cómo acceder a la propiedad del objeto a través de un alcance aislado sin un enlace bidireccional? creo que es útil. use el alcance aislado y llame al alcance principal a través de $ scope. $ parent
JJbang

Respuestas:

98

La solución de Marko funciona bien .

Para contrastar con la forma angular recomendada (como se muestra en plunkr de treeface) es usar una expresión de devolución de llamada que no requiere definir el expressionHandler. En el cambio de ejemplo de marko:

En plantilla

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

En función de enlace directivo

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

Esto tiene una desventaja en comparación con la solución de marko: en la primera carga, la funciónMethodToBeCalled se invocará con myParam === undefined.

Puede encontrar un ejemplo de trabajo en @treeface Plunker

johans
fuente
De hecho, este patrón de proporcionar el nombre de la función con parámetro en el atributo personalizado parece ser la forma más fácil y robusta de definir "funciones de devolución de llamada" en directivas ... ¡¡gracias !!
rekna
3
Es muy confuso llamar a "setProduct" 2 cosas diferentes - atributo y función de alcance, hace que sea realmente difícil entender cuál es y dónde.
Dmitri Zaitsev
Lo que es confuso es por qué la respuesta correcta es usar un alcance aislado, pero la pregunta no. ¿O me estoy perdiendo algo?
j_walker_dev
De mis pruebas usando este código en angular 1.2.28, funciona bien. Mis pruebas no llaman a theMethodToBeCalled con undefined en la primera carga. Tampoco el ejemplo del plunker. Esto no parece ser un problema. En otras palabras, este parece ser el enfoque correcto cuando desea pasar parámetros al enlace de la directiva (en un ámbito aislado). Recomiendo actualizar el comentario sobre la desventaja y myParam === undefined.
jazeee
Este "Esto tiene una desventaja en comparación con la solución de marko: en la primera carga, la funciónMethodToBeCalled se invocará con myParam === undefined" no está sucediendo en mi caso ... supongo que hay un contexto implícito aquí
Victor
95

Solo para agregar algo de información a las otras respuestas, usar &es una buena manera si necesita un alcance aislado.

La principal desventaja de la solución de marko es que te obliga a crear un ámbito aislado en un elemento, pero solo puedes tener uno de esos en un elemento (de lo contrario, te encontrarás con un error angular: varias directivas [directive1, directive2] preguntando para alcance aislado )

Esto significa tu :

  • No se puede usar en un elemento que tiene un alcance aislado.
  • no se pueden usar dos directivas con esta solución en el mismo elemento

Dado que la pregunta original utiliza una directiva, restrict:'A'ambas situaciones pueden surgir con bastante frecuencia en aplicaciones más grandes, y utilizar un ámbito aislado aquí no es una buena práctica y también es innecesario. De hecho, rekna tenía una buena intuición en este caso, y casi lo hizo funcionar, lo único que estaba haciendo mal era llamar mal a la función $ parsed (vea lo que devuelve aquí: https://docs.angularjs.org/api/ ng / service / $ parse ).

TL; DR; Código de pregunta fijo

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

y el codigo

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
fuente
11
+1 De hecho, no es necesario aislar el visor, ¡mucho más limpio!
Dmitri Zaitsev
¿Qué pasa si el atributo contiene solo el nombre del método, como <div my-method = 'theMethodToBeCalled'> </div> o una función en línea como <div my-method = 'alert ("hi");'> </div>
Jonathan.
1
Si tiene problemas con cualquiera de estos enfoques, tenga en cuenta que el método que se llama debe estar disponible en el ámbito que pasa a la función devuelta $parse.
ragamufin
Esta respuesta es exactamente lo que estaba buscando +1
grimmdude
Cuál es el evento"? ¿Cómo puedo ejecutar la función en un div hijo?
Shlomo
88

Sin saber exactamente lo que quiere hacer ... pero aún así, hay una posible solución.

Cree un ámbito con una propiedad '&' - en el ámbito local. "Proporciona una forma de ejecutar una expresión en el contexto del ámbito principal" (consulte la documentación de la directiva para obtener más detalles).

También noté que usaste una función de vinculación abreviada y metiste atributos de objeto allí. No puedes hacer eso. Es más claro (en mi humilde opinión) simplemente devolver el objeto de definición de directiva. Vea mi código a continuación.

Aquí hay una muestra de código y un violín .

<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
fuente
Se llama a la función, cuando configuro $ scope.id = id dentro deMethodToBeCalled, la vista no se actualiza. Probablemente necesito envolver expressionHandler (id) dentro de scope.apply.
rekna
2
Impresionante, esto me ayudó a encontrar la solución a mi problema. Vale la pena mencionar que solo necesita hacer esto en el nivel superior si está haciendo un nido más profundo de directivas. Considere esto: plnkr.co/edit/s3y67iGL12F2hDER2RNl?p=preview donde paso el método a través de dos directivas.
treeface
3
Y aquí hay una segunda forma (quizás más angular) de hacerlo: plnkr.co/edit/r9CV9Y1RWRuse4RwFPka?p=preview
treeface
1
¿Cómo puedo hacerlo sin aislamiento de alcance?
Evan Lévesque
3
+1 porque esta solución me enseñó que necesito llamar a scope.method (); primero para obtener la referencia real al método.
Sal
6

Puede crear una directiva que ejecute una llamada a función con parámetros utilizando attrName: "&"para hacer referencia a la expresión en el ámbito externo.

Queremos reemplazar la ng-clickdirectiva con ng-click-x:

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

Si tuviéramos este alcance:

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

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

Podríamos escribir nuestra directiva así:

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

Aquí hay una demostración en vivo: http://plnkr.co/edit/4QOGLD?p=info

f1lt3r
fuente
2

Esto es lo que funcionó para mí.

Html usando la directiva

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

Html de la directiva: orderitem.directive.html

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

Alcance de la directiva:

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

Mi solución:

  1. en polímero provocar un evento (p. ej. complete)
  2. definir una directiva que vincule el evento con la función de control

Directiva

/*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);
                    })
                }
            };
        }]);
});

Componente polimérico

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

Página

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

searchData es la función de control

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

}
venergíaco
fuente
-2

Esto debería funcionar.

<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
fuente