Uma diretiva angular pode passar argumentos para funções em expressões especificadas nos atributos da diretiva?

160

Eu tenho uma diretiva de formulário que usa um callbackatributo especificado com um escopo isolado:

scope: { callback: '&' }

Ele fica dentro de um, ng-repeatentão a expressão que eu passo inclui ido objeto como argumento para a função de retorno de chamada:

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

Quando terminei com a diretiva, ela chama $scope.callback()de sua função de controlador. Na maioria dos casos, isso é bom e é tudo o que quero fazer, mas às vezes eu gostaria de adicionar outro argumento de dentro dele directive.

Existe uma expressão angular que permita isso $scope.callback(arg2):, resultando em callbackser chamado com arguments = [item.id, arg2]?

Caso contrário, qual é a melhor maneira de fazer isso?

Eu descobri que isso funciona:

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

Com

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

e a diretiva chamando

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

Mas não acho que seja particularmente interessante e envolve colocar coisas extras no escopo isolado.

Existe uma maneira melhor?

Playground Plunker aqui (tenha o console aberto).

Ed Hinchliffe
fonte
O atributo que nomeia "retorno de chamada =" engana. É realmente uma avaliação de retorno de chamada, não um retorno de chamada propriamente dito.
Dmitri Zaitsev
@DmitriZaitsev é uma expressão angular de retorno de chamada que avaliará uma função JavaScript. Eu acho que é bastante óbvio que não é uma função JavaScript em si. É apenas preferência, mas eu preferiria não ter que sufocar todos os meus atributos com "-expression". Isso é consistente com a ngAPI, por exemplo, ng-click="someFunction()"é uma expressão que avalia a execução de uma função.
Ed Hinchliffe
Eu nunca vi a expressão Angular chamada "retorno de chamada". É sempre uma função que você passa a ser chamada, de onde o nome. Você ainda usa uma função chamada "retorno de chamada" no seu exemplo, para tornar as coisas ainda mais confusas.
Dmitri Zaitsev
Não tenho certeza se você está confuso ou estou. No meu exemplo $scope.callbacké definido pelo callback="someFunction"atributo e a scope: { callback: '=' }propriedade do objeto de definição de diretiva. $scope.callback é uma função a ser chamada posteriormente. O valor real do atributo é obviamente uma string - esse é sempre o caso do HTML.
precisa
Você nomeia ambos os atributos e funciona da mesma maneira - "retorno de chamada". Essa é a receita para a confusão. Fácil de evitar realmente.
Dmitri Zaitsev

Respostas:

215

Se você declarar seu retorno de chamada conforme mencionado por @ lex82 como

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

Você pode chamar o método de retorno de chamada no escopo da diretiva com o mapa de objetos e isso faria a ligação corretamente. Gostar

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

sem requerer $ parse. Veja meu violino (log do console) http://jsfiddle.net/k7czc/2/

Atualização : Há um pequeno exemplo disso na documentação :

& ou & attr - fornece uma maneira de executar uma expressão no contexto do escopo pai. Se nenhum nome de atributo for especificado, será assumido que o nome do atributo seja o mesmo que o nome local. Dada a definição de escopo e widget: {localFn: '& myAttr'}, então isole a propriedade escopo localFn apontará para um wrapper de função para a expressão count = count + value. Frequentemente, é desejável passar dados do escopo isolado por meio de uma expressão e para o escopo pai, isso pode ser feito passando um mapa de nomes e valores de variáveis ​​locais para o wrapper de expressão fn. Por exemplo, se a expressão for incremento (quantidade), podemos especificar o valor da quantidade chamando o localFn como localFn ({amount: 22}).

Chandermani
fonte
4
Muito agradável! Isso está documentado em algum lugar?
ach
12
Eu não acho que é uma boa solução porque na definição directiva, às vezes você não sabe o que é o parâmetro para passar.
omgpop
Esta é uma boa solução e obrigado por isso, mas acredito que a resposta precisa de um pouco de maré. Quem é lex82 e o que ele mencionou?
Wtower
Abordagem interessante. Embora o que acontece quando você deseja permitir que qualquer função com QUALQUER parâmetro (ou múltiplo) seja passada? Você não conhece nada da função nem de seus parâmetros e precisa executá-la em algum evento dentro da diretiva. Como lidar com isto? Por exemplo, em uma diretiva, você pode ter onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis
58

Nada de errado com as outras respostas, mas uso a seguinte técnica ao passar funções em um atributo de diretiva.

Deixe os parênteses ao incluir a diretiva no seu html:

<my-directive callback="someFunction" />

Em seguida, "desembrulhe" a função no link ou controlador da sua diretiva. aqui está um exemplo:

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

A etapa "desembrulhar" permite que a função seja chamada usando uma sintaxe mais natural. Ele também garante que a diretiva funcione corretamente, mesmo quando aninhada em outras diretivas que possam passar na função. Se você não desembrulhou, se tiver um cenário como este:

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

Então você terminaria com algo assim em sua diretiva interna:

callback()()()(data); 

O que falharia em outros cenários de aninhamento.

Adaptei essa técnica a partir de um excelente artigo de Dan Wahlin em http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

Adicionei a etapa de desembrulhar para tornar a chamada da função mais natural e resolver o problema de aninhamento que encontrei em um projeto.

ItsCosmo
fonte
2
Uma boa abordagem, mas não consigo usar o thisponteiro dentro do método de retorno de chamada, porque ele usa o escopo da diretiva. Eu estou usando Typescript e meus olhares de retorno de chamada como esta: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: se você possui diretivas aninhadas e deseja propagar o retorno de chamada para cima, é necessário desembrulhar cada diretiva, não apenas aquela que aciona o retorno de chamada.
Episodex 28/08/2015
43

Na diretiva ( myDirective):

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

No modelo de diretiva:

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

Na fonte:

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

... onde myFunctioné definido no controlador.

Observe que paramno modelo de diretiva se vincula perfeitamente à paramfonte e está definido como item.


Para chamar de dentro da linkpropriedade de uma diretiva ("dentro" dela), use uma abordagem muito semelhante:

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Ben
fonte
Enquanto tiver In source: bound-function = 'myFunction (obj1.param, obj2.param)'>, então como proceder?
Ankit Pandey
15

Sim, existe uma maneira melhor: você pode usar o serviço $ parse em sua diretiva para avaliar uma expressão no contexto do escopo pai, enquanto vincula certos identificadores na expressão a valores visíveis apenas dentro da diretiva:

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

Adicione esta linha à função de link da diretiva na qual você pode acessar os atributos da diretiva.

Seu atributo de retorno de chamada pode ser definido como o callback = "callback(item.id, arg2)"arg2 é vinculado ao yourSecondArgument pelo serviço $ parse dentro da diretiva. Diretivas como ng-clickpermitem acessar o evento click por meio do $eventidentificador dentro da expressão passada para a diretiva usando exatamente esse mecanismo.

Observe que você não precisa tornar callbackum membro do seu escopo isolado com esta solução.

lex82
fonte
3
O uso scope.$parentfaz com que a diretiva "vaze" - ela "conhece" muito do mundo exterior, o que um componente encapsulado bem projetado não deveria.
Dmitri Zaitsev
3
Bem, ele sabe que tem um escopo pai, mas não acessa um campo específico no escopo, então acho que isso é tolerável.
Lex82
0

Para mim, seguinte trabalho:

na diretiva declare assim:

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

No modelo de diretiva, use-o da seguinte maneira:

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

E então, quando você usar a diretiva, passe a função da seguinte maneira:

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

Você passa a função por sua declaração e ela é chamada de diretiva e os parâmetros são preenchidos.

michal.jakubeczy
fonte