¿Puede una directiva angular pasar argumentos a funciones en expresiones especificadas en los atributos de la directiva?

160

Tengo una directiva de formulario que usa un callbackatributo especificado con un alcance de aislamiento:

scope: { callback: '&' }

Se encuentra dentro de un, ng-repeatpor lo que la expresión que paso incluye el iddel objeto como un argumento para la función de devolución de llamada:

<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>

Cuando termine con la directiva, llama $scope.callback()desde su función de controlador. Para la mayoría de los casos, esto está bien, y es todo lo que quiero hacer, pero a veces me gustaría agregar otro argumento desde dentro directive.

¿Existe una expresión angular que permita esto: que $scope.callback(arg2)resulte en callbackser llamado con arguments = [item.id, arg2]?

Si no, ¿cuál es la mejor manera de hacer esto?

He descubierto que esto funciona:

<directive 
  ng-repeat = "item in stuff" 
  callback = "callback" 
  callback-arg="item.id"/>

Con

scope { callback: '=', callbackArg: '=' }

y la llamada directiva

$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );

Pero no creo que sea particularmente bueno e implica poner cosas adicionales en el ámbito de aislamiento.

¿Hay una mejor manera?

Parque infantil Plunker aquí (tener la consola abierta).

Ed Hinchliffe
fuente
El atributo que nombra "devolución de llamada =" induce a error. Es realmente una evaluación de devolución de llamada, no una devolución de llamada en sí.
Dmitri Zaitsev
@DmitriZaitsev es una expresión angular de devolución de llamada que evaluará a una función de JavaScript. Creo que es bastante obvio que no es una función de JavaScript en sí misma. Es solo preferencia, pero preferiría no tener que sufijar todos mis atributos con "-expression". Esto es coherente con la ngAPI, por ejemplo, ng-click="someFunction()"es una expresión que evalúa la ejecución de una función.
Ed Hinchliffe
Nunca he visto una expresión angular llamada "devolución de llamada". Siempre es una función que pasa a llamarse, de ahí el nombre. Incluso utiliza una función llamada "devolución de llamada" en su ejemplo, para hacer las cosas aún más confusas.
Dmitri Zaitsev
No estoy seguro de si estás confundido o yo sí. En mi ejemplo, $scope.callbacklo establece el callback="someFunction"atributo y la scope: { callback: '=' }propiedad del objeto de definición de directiva. $scope.callback es una función que se llamará en una fecha posterior. El valor real del atributo es obviamente una cadena, que siempre es el caso con HTML.
Ed Hinchliffe
Nombra tanto el atributo como la función: "devolución de llamada". Esa es la receta para la confusión. Fácil de evitar realmente.
Dmitri Zaitsev

Respuestas:

215

Si declara su devolución de llamada como lo menciona @ lex82 como

callback = "callback(item.id, arg2)"

Puede llamar al método de devolución de llamada en el ámbito de la directiva con el mapa de objetos y haría el enlace correctamente. Me gusta

scope.callback({arg2:"some value"});

sin requerir $ parse. Ver mi violín (registro de consola) http://jsfiddle.net/k7czc/2/

Actualización : hay un pequeño ejemplo de esto en la documentación :

& or & attr: proporciona una forma de ejecutar una expresión en el contexto del ámbito primario. Si no se especifica un nombre de atributo, se supone que el nombre del atributo es el mismo que el nombre local. Dada la definición del alcance del widget: {localFn: '& myAttr'}, luego aislar la propiedad del alcance localFn apuntará a un contenedor de funciones para la expresión count = count + value. A menudo es deseable pasar datos del ámbito aislado a través de una expresión y al ámbito primario, esto se puede hacer pasando un mapa de nombres y valores de variables locales en el contenedor de expresiones fn. Por ejemplo, si la expresión es incremento (cantidad), entonces podemos especificar el valor de la cantidad llamando a localFn como localFn ({cantidad: 22}).

Chandermani
fuente
44
¡Muy agradable! ¿Está documentado en alguna parte?
ach
12
No creo que sea una buena solución porque, en la definición de la directiva, a veces no
sabrías
Esta es una buena solución y gracias por eso, pero creo que la respuesta necesita un poco de ayuda. ¿Quién es lex82 y qué ha mencionado?
Wtower
Enfoque interesante Aunque, ¿qué sucede cuando desea permitir que se pase cualquier función con CUALQUIER parámetro (o múltiple)? No sabe nada de la función ni de sus parámetros y necesita ejecutarla en algún evento dentro de la directiva. ¿Cómo hacerlo? Por ejemplo, en una directiva, podría tener onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis
58

No hay nada malo con las otras respuestas, pero uso la siguiente técnica cuando paso funciones en un atributo directivo.

Deja el paréntesis al incluir la directiva en tu html:

<my-directive callback="someFunction" />

Luego "desenvuelva" la función en el enlace o controlador de su directiva. Aquí hay un ejemplo:

app.directive("myDirective", function() {

    return {
        restrict: "E",
        scope: {
            callback: "&"                              
        },
        template: "<div ng-click='callback(data)'></div>", // call function this way...
        link: function(scope, element, attrs) {
            // unwrap the function
            scope.callback = scope.callback(); 

            scope.data = "data from somewhere";

            element.bind("click",function() {
                scope.$apply(function() {
                    callback(data);                        // ...or this way
                });
            });
        }
    }
}]);    

El paso "desenvolver" permite llamar la función utilizando una sintaxis más natural. También garantiza que la directiva funcione correctamente incluso cuando está anidada dentro de otras directivas que pueden pasar la función. Si no desenvolvió, entonces si tiene un escenario como este:

<outer-directive callback="someFunction" >
    <middle-directive callback="callback" >
        <inner-directive callback="callback" />
    </middle-directive>
</outer-directive>

Entonces terminarías con algo como esto en tu directiva interna:

callback()()()(data); 

Lo cual fallaría en otros escenarios de anidación.

Adapte esta técnica de un excelente artículo de Dan Wahlin en http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

Agregué el paso de desenvolver para hacer que llamar a la función sea más natural y para resolver el problema de anidación que había encontrado en un proyecto.

ItsCosmo
fuente
2
Un buen enfoque, pero no puedo usar el thispuntero dentro del método de devolución de llamada, porque usa el alcance de la directiva. Estoy usando Typecript y mi devolución de llamada se ve así:public validateFirstName(firstName: string, fieldName: string): ng.IPromise<boolean> { var deferred = this.mQService.defer<boolean>(); ... .then(() => deferred.resolve(true)) .catch((msg) => { deferred.reject(false); }); return deferred.promise; }
ndee
1
Aviso: si tiene directivas anidadas y desea propagar la devolución de llamada hacia arriba, debe desenvolver cada directiva, no solo la que activa la devolución de llamada.
Episodio
43

En la directiva ( myDirective):

...
directive.scope = {  
    boundFunction: '&',
    model: '=',
};
...
return directive;

En plantilla directiva:

<div 
data-ng-repeat="item in model"  
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>

En fuente:

<my-directive 
model='myData' 
bound-function='myFunction(param)'>
</my-directive>

... donde myFunctionse define en el controlador.

Tenga paramen cuenta que en la plantilla de directiva se enlaza perfectamente paramen la fuente y se establece en item.


Para llamar desde dentro de la linkpropiedad de una directiva ("dentro" de ella), utilice un enfoque muy similar:

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Ben
fuente
Mientras tiene In source: bound-function = 'myFunction (obj1.param, obj2.param)'> entonces, ¿cómo proceder?
Ankit Pandey
15

Sí, hay una mejor manera: puede usar el servicio $ parse en su directiva para evaluar una expresión en el contexto del ámbito primario mientras vincula ciertos identificadores en la expresión a valores visibles solo dentro de su directiva:

$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });

Agregue esta línea a la función de enlace de la directiva donde puede acceder a los atributos de la directiva.

Su atributo de devolución de llamada se puede configurar como callback = "callback(item.id, arg2)"porque arg2 está vinculado a yourSecondArgument por el servicio $ parse dentro de la directiva. Directivas como ng-clickpermitirle acceder al evento click a través del $eventidentificador dentro de la expresión pasada a la directiva usando exactamente este mecanismo.

Tenga en cuenta que no tiene que hacer callbackun miembro de su ámbito aislado con esta solución.

lex82
fuente
3
El uso scope.$parenthace que la directiva "tenga fugas": "conoce" demasiado del mundo exterior, que un componente encapsulado bien diseñado no debería.
Dmitri Zaitsev
3
Bueno, sabe que tiene un alcance principal, pero no accede a un campo particular en el alcance, por lo que creo que esto es tolerable.
lex82
0

Para mí lo siguiente funcionó:

en la directiva declararlo así:

.directive('myDirective', function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            myFunction: '=',
        },
        templateUrl: 'myDirective.html'
    };
})  

En la plantilla directiva, úsela de la siguiente manera:

<select ng-change="myFunction(selectedAmount)">

Y luego, cuando use la directiva, pase la función de esta manera:

<data-my-directive
    data-my-function="setSelectedAmount">
</data-my-directive>

Se pasa la función por su declaración y se llama desde la directiva y se completan los parámetros.

michal.jakubeczy
fuente