Dyrektywa angularjs wywołuje funkcję określoną w atrybucie i przekazuje do niej argument


103

Chcę utworzyć dyrektywę, która łączy się z atrybutem. Atrybut określa funkcję, która powinna zostać wywołana w zakresie. Ale chcę również przekazać argument do funkcji, która jest określona w funkcji łącza.

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

W funkcji link łączę się ze zdarzeniem jQuery, które przekazuje argument, który muszę przekazać do funkcji:

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

Bez przekazywania identyfikatora mogę sprawić, że zacznie działać, ale jak tylko spróbuję przekazać argument, funkcja nie jest już wywoływana


jak uzyskać dostęp do właściwości obiektu przez izolowany zakres bez dwukierunkowego wiązania? myślę, że to pomocne. użyj zakresu izolowanego i wywołaj zakres nadrzędny za pośrednictwem $ scope. $ parent
JJbang

Odpowiedzi:


98

Rozwiązanie Marko działa dobrze .

W odróżnieniu od zalecanego sposobu Angulara (jak pokazuje plunkr treeface) jest użycie wyrażenia zwrotnego, które nie wymaga definiowania wyrażenia expressionHandler. W przykładzie Marko zmień:

W szablonie

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

W funkcji linku dyrektywy

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

Ma to jedną wadę w porównaniu z rozwiązaniem marko - przy pierwszym załadowaniu funkcjaMethodToBeCalled zostanie wywołana z myParam === undefined.

Działający przykład można znaleźć pod adresem @treeface Plunker


Rzeczywiście, ten wzorzec dostarczania nazwy funkcji z parametrem w atrybucie niestandardowym wydaje się być najłatwiejszym i najbardziej niezawodnym sposobem definiowania „funkcji zwrotnych” w dyrektywach… thx !!
rekna

3
Wywołanie „setProduct” dwóch różnych rzeczy - funkcji atrybutu i zakresu - jest bardzo mylące, co sprawia, że ​​naprawdę trudno jest zrozumieć, która z nich jest gdzie.
Dmitri Zaitsev

Niewykluczone jest to, dlaczego właściwą odpowiedzią jest użycie zakresu izolowanego, a pytanie nie. A może coś mi brakuje?
j_walker_dev

Z moich testów wykorzystujących ten kod w kątowym 1.2.28, działa dobrze. Moje testy nie wywołują metody theMethodToBeCalled z wartością undefined przy pierwszym ładowaniu. Ani przykład plunkera. Nie wydaje się to stanowić problemu. Innymi słowy, wydaje się, że jest to właściwe podejście, gdy chcesz przekazać parametry do łącza dyrektywy (w zakresie izolowanym). Polecam zaktualizować komentarz dotyczący wady i myParam === undefined.
jazeee

To "To ma jedną wadę w porównaniu z rozwiązaniem Marko - przy pierwszym załadowaniu funkcjaMethodToBeCalled zostanie wywołana z myParam === undefined" nie dzieje się w moim przypadku ... myślę, że jest tu jakiś ukryty kontekst
Victor

95

Aby dodać trochę informacji do innych odpowiedzi - użycie &jest dobrym sposobem, jeśli potrzebujesz izolowanego zakresu.

Główną wadą rozwiązania Marko jest to, że zmusza cię do utworzenia izolowanego zakresu na elemencie, ale możesz mieć tylko jeden z nich na elemencie (w przeciwnym razie napotkasz błąd kątowy: Wiele dyrektyw [dyrektywa1, dyrektywa2] pyta dla zakresu izolowanego )

To znaczy ty :

  • nie można jej użyć na elemencie, który sam ma izolowany zasięg
  • nie można użyć dwóch dyrektyw z tym rozwiązaniem w tym samym elemencie

Ponieważ pierwotne pytanie używa dyrektywy, a restrict:'A'obie sytuacje mogą wystąpić dość często w większych aplikacjach, a użycie izolowanego zakresu tutaj nie jest dobrą praktyką, a także jest niepotrzebne. W rzeczywistości rekna miał dobrą intuicję w tym przypadku i prawie działał, jedyną rzeczą, którą robił źle, było błędne wywołanie funkcji $ parsed (zobacz, co zwraca tutaj: https://docs.angularjs.org/api/ ng / service / $ parse ).

TL; DR; Naprawiono kod pytania

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

i kod

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

11
+1 Rzeczywiście - nie ma potrzeby izolowania lunety - znacznie czystsze!
Dmitri Zaitsev

co, jeśli atrybut zawiera tylko nazwę metody, na przykład <div my-method = 'theMethodToBeCalled'> </div> lub funkcję wbudowaną, taką jak <div my-method = 'alert ("hi");'> </div>
Jonathan.

1
Jeśli masz problem z którymkolwiek z tych podejść, pamiętaj, że wywoływana metoda musi być dostępna w zakresie, z którego przekazujesz funkcję zwróconą $parse.
ragamufin

Ta odpowiedź jest dokładnie tym, czego szukałem +1
grimmdude

co to jest „theEvent”? Jak mogę wykonać funkcję na podrzędnym elemencie DIV?
Shlomo,

88

Nie wiem dokładnie, co chcesz zrobić ... ale nadal jest możliwe rozwiązanie.

Utwórz zakres z właściwością „&” w zakresie lokalnym. „Zapewnia sposób wykonania wyrażenia w kontekście zakresu nadrzędnego” (szczegóły w dokumentacji dyrektywy ).

Zauważyłem również, że użyłeś skróconej funkcji łączącej i umieściłeś tam atrybuty obiektów. Nie możesz tego zrobić. Bardziej jasne jest (imho), aby po prostu zwrócić obiekt definicji dyrektywy. Zobacz mój kod poniżej.

Oto przykładowy kod i skrzypce .

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

Funkcja zostaje wywołana, kiedy ustawię $ scope.id = id wewnątrz theMethodToBeCalled, widok nie jest aktualizowany. Prawdopodobnie muszę zawijać wyrażenie expressionHandler (id) wewnątrz scope.apply.
rekna

2
Niesamowite, że pomogło mi to znaleźć rozwiązanie mojego problemu. Warto wspomnieć, że musisz to zrobić tylko na najwyższym poziomie, jeśli robisz głębszą grupę dyrektyw. Rozważ to: plnkr.co/edit/s3y67iGL12F2hDER2RNl?p=preview gdzie przekazuję metodę przez dwie dyrektywy.
treeface

3
A oto drugi (być może bardziej kanciasty) sposób na zrobienie tego: plnkr.co/edit/r9CV9Y1RWRuse4RwFPka?p=preview
treeface

1
Jak mogę to zrobić bez izolacji zakresu?
Evan Lévesque

3
+1, ponieważ to rozwiązanie nauczyło mnie, że muszę wywołać metodę scope.method (); najpierw, aby uzyskać rzeczywiste odniesienie do metody.
Sal

6

Możesz utworzyć dyrektywę, która wykonuje wywołanie funkcji z parametrami, używając attrName: "&"do odwoływania się do wyrażenia w zakresie zewnętrznym.

Chcemy zastąpić ng-clickdyrektywę ng-click-x:

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

Gdybyśmy mieli taki zakres:

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

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

Możemy napisać naszą dyrektywę w ten sposób:

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

Oto demo na żywo: http://plnkr.co/edit/4QOGLD?p=info


2

Oto, co zadziałało dla mnie.

Html za pomocą dyrektywy

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

Html dyrektywy: orderitem.directive.html

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

Zakres dyrektywy:

scope: {
    orderItem: '=',
    remove: "&",

0

Moje rozwiązanie:

  1. na polimerze wywołać zdarzenie (np. complete)
  2. zdefiniować dyrektywę łączącą zdarzenie z funkcją sterującą

Dyrektywa

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

Komponent polimerowy

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

Strona

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

searchData jest funkcją kontrolną

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

}

-2

To powinno działać.

<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); };
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.