função de chamada de diretiva angularjs especificada no atributo e passar um argumento para ela

102

Quero criar uma diretiva que vincule a um atributo. O atributo especifica a função que deve ser chamada no escopo. Mas também quero passar um argumento para a função que é determinada dentro da função de link.

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

Na função de link, eu ligo a um evento jQuery, que passa um argumento que preciso passar para a função:

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

Sem passar o id posso fazer funcionar, mas assim que tento passar um argumento, a função não é mais chamada

Rekna
fonte
como acessar a propriedade do objeto via escopo isolado sem ligação bidirecional? eu acho que é útil. use o escopo isolado e chame o escopo pai por meio de $ scope. $ parent
JJbang

Respostas:

98

A solução de Marko funciona bem .

Para contrastar com a forma Angular recomendada (como mostrado pelo plunkr da face da árvore) é usar uma expressão de retorno de chamada que não requer a definição de expressionHandler. No exemplo de mudança de marko:

Em modelo

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

Na função de ligação diretiva

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

Isso tem uma desvantagem em comparação com a solução de marko - no primeiro carregamento, a funçãoMethodToBeCalled será chamada com myParam === indefinido.

Um exemplo prático pode ser encontrado em @treeface Plunker

johans
fonte
De fato, esse padrão de fornecer o nome da função com parâmetro no atributo personalizado parece ser a maneira mais fácil e robusta de definir "funções de retorno de chamada" em diretivas ... thx !!
rekna de
3
É muito confuso chamar "setProduct" 2 coisas diferentes - atributo e função de escopo, o que torna muito difícil entender qual está onde.
Dmitri Zaitsev
O que é confuso é por que a resposta certa é usar escopo isolado, mas a pergunta não. Ou eu estou esquecendo de alguma coisa?
j_walker_dev
Pelos meus testes usando este código no angular 1.2.28, ele funciona bem. Meus testes não chamam theMethodToBeCalled com undefined no primeiro carregamento. Nem o exemplo do êmbolo. Isso não parece ser um problema. Em outras palavras, essa parece ser a abordagem correta quando você deseja passar parâmetros para o link de diretiva (em um escopo isolado). Eu recomendo atualizar o comentário sobre a desvantagem e myParam === indefinido.
jazeee
Este "Isso tem uma desvantagem em comparação com a solução de marko - no primeiro carregamento, a função MetodoParaBeCalled será chamada com myParam === indefinido" não está acontecendo no meu caso ... acho que há algum contexto implícito aqui
Victor
95

Apenas para adicionar algumas informações às outras respostas - usar &é uma boa maneira se você precisar de um escopo isolado.

A principal desvantagem da solução de marko é que ele força você a criar um escopo isolado em um elemento, mas você só pode ter um deles em um elemento (caso contrário, você encontrará um erro angular: várias diretivas [diretiva1, diretiva2] perguntando para escopo isolado )

Isso significa que você:

  • não pode usá-lo em um elemento que tem um escopo isolado
  • não pode usar duas diretivas com esta solução no mesmo elemento

Uma vez que a questão original usa uma diretiva com restrict:'A'ambas as situações, pode surgir muitas vezes em aplicações maiores, e usar um escopo isolado aqui não é uma boa prática e também desnecessário. Na verdade, rekna teve uma boa intuição neste caso, e quase a fez funcionar, a única coisa que ele estava fazendo de errado foi chamar a função $ parsed de forma errada (veja o que ela retorna aqui: https://docs.angularjs.org/api/ ng / service / $ parse ).

TL; DR; Código de pergunta corrigido

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

e o código

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
fonte
11
+1 Na verdade - não há necessidade de isolar o escopo - muito mais limpo!
Dmitri Zaitsev
e se o atributo contiver apenas o nome do método, como <div my-method = 'theMethodToBeCalled'> </div> ou uma função embutida como <div my-method = 'alert ("hi");'> </div>
Jonathan.
1
Se você estiver tendo problemas com qualquer uma dessas abordagens, lembre-se de que o método que está sendo chamado deve estar disponível no escopo que você passou para a função de onde retornou $parse.
ragamufin de
Esta resposta é exatamente o que eu estava procurando +1
grimmdude
o que é "theEvent"? Como posso executar a função em um div filho?
Shlomo
88

Não sabendo exatamente o que você quer fazer ... mas ainda aqui está uma possível solução.

Crie um escopo com uma propriedade '&' - no escopo local. Ele "fornece uma maneira de executar uma expressão no contexto do escopo pai" (consulte a documentação da diretiva para obter detalhes).

Também notei que você usou uma função de ligação abreviada e colocou atributos de objeto nela. Você não pode fazer isso. É mais claro (imho) retornar apenas o objeto de definição de diretiva. Veja meu código abaixo.

Aqui está um exemplo de código e um violino .

<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
fonte
A função é chamada, quando eu defino $ scope.id = id dentro de theMethodToBeCalled, a visualização não é atualizada. Provavelmente preciso envolver expressionHandler (id) dentro de scope.apply.
rekna de
2
Incrível, isso me ajudou a encontrar a solução para o meu problema. Vale a pena mencionar que você só precisa fazer isso no nível superior se estiver fazendo um ninho de diretivas mais profundo. Considere o seguinte: plnkr.co/edit/s3y67iGL12F2hDER2RNl?p=preview onde passo o método por meio de duas diretivas.
treeface
3
E aqui está um segundo (talvez mais angular) maneira de fazê-lo: plnkr.co/edit/r9CV9Y1RWRuse4RwFPka?p=preview
treeface
1
Como posso fazer isso sem isolamento de escopo?
Evan Lévesque
3
+1 porque esta solução me ensinou que eu preciso chamar scope.method (); primeiro para obter a referência real do método.
Sal
6

Você pode criar uma diretiva que execute uma chamada de função com parâmetros usando o attrName: "&"para fazer referência à expressão no escopo externo.

Queremos substituir a ng-clickdiretiva por ng-click-x:

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

Se tivéssemos este escopo:

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

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

Poderíamos escrever nossa diretiva assim:

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

Aqui está uma demonstração ao vivo: http://plnkr.co/edit/4QOGLD?p=info

f1lt3r
fonte
2

Aqui está o que funcionou para mim.

Html usando a diretiva

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

Html da diretiva: orderitem.directive.html

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

Âmbito da diretiva:

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

Minha solução:

  1. no polímero gerar um evento (por exemplo complete)
  2. definir uma diretiva ligando o evento à função de controle

Diretriz

/*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 de polímero

<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 é a função de controle

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

}
venérgico
fonte
-2

Isso deve 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
fonte