Jak wywołać metodę zdefiniowaną w dyrektywie AngularJS?

297

Mam dyrektywę, oto kod:

.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {

            var center = new google.maps.LatLng(50.1, 14.4); 
            $scope.map_options = {
                zoom: 14,
                center: center,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            // create map
            var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
            var dirService= new google.maps.DirectionsService();
            var dirRenderer= new google.maps.DirectionsRenderer()

            var showDirections = function(dirResult, dirStatus) {
                if (dirStatus != google.maps.DirectionsStatus.OK) {
                    alert('Directions failed: ' + dirStatus);
                    return;
                  }
                  // Show directions
                dirRenderer.setMap(map);
                //$scope.dirRenderer.setPanel(Demo.dirContainer);
                dirRenderer.setDirections(dirResult);
            };

            // Watch
            var updateMap = function(){
                dirService.route($scope.dirRequest, showDirections); 
            };    
            $scope.$watch('dirRequest.origin', updateMap);

            google.maps.event.addListener(map, 'zoom_changed', function() {
                $scope.map_options.zoom = map.getZoom();
              });

            dirService.route($scope.dirRequest, showDirections);  
        }
    }
})

Chciałbym wezwać updateMap()użytkownika do działania. Przycisk akcji nie znajduje się na dyrektywie.

Jak najlepiej dzwonić updateMap()z kontrolera?

Mcbjam
źródło
11
Mała uwaga: konwencja nie używa znaku dolara dla „zakresu” w funkcji link, ponieważ zakres nie jest wprowadzany, ale przekazywany jako zwykły argument.
Noam

Odpowiedzi:

369

Jeśli chcesz użyć izolowanych zakresów, możesz przekazać obiekt sterujący za pomocą dwukierunkowego wiązania =zmiennej z zakresu kontrolera. Możesz także kontrolować kilka instancji tej samej dyrektywy na stronie z tym samym obiektem kontrolnym.

angular.module('directiveControlDemo', [])

.controller('MainCtrl', function($scope) {
  $scope.focusinControl = {};
})

.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{internalControl}}</div>',
    scope: {
      control: '='
    },
    link: function(scope, element, attrs) {
      scope.internalControl = scope.control || {};
      scope.internalControl.takenTablets = 0;
      scope.internalControl.takeTablet = function() {
        scope.internalControl.takenTablets += 1;
      }
    }
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
  <div ng-controller="MainCtrl">
    <button ng-click="focusinControl.takeTablet()">Call directive function</button>
    <p>
      <b>In controller scope:</b>
      {{focusinControl}}
    </p>
    <p>
      <b>In directive scope:</b>
      <focusin control="focusinControl"></focusin>
    </p>
    <p>
      <b>Without control object:</b>
      <focusin></focusin>
    </p>
  </div>
</div>

Oliver Wienand
źródło
11
+1 W ten sposób tworzę interfejsy API dla moich komponentów wielokrotnego użytku w Angular.
romiem
5
To jest czystsze niż zaakceptowana odpowiedź i +1 za odniesienie do simpsonów, jeśli się nie mylę
Blake Miller
44
Właśnie w ten sposób rozwiązałem ten sam problem. Działa, ale wygląda na włamanie ... Chciałbym, żeby firma Angular miała na to lepsze rozwiązanie.
Dema
1
Uczę się kątowego, więc moja opinia może nie mieć większego znaczenia, ale uważam, że to podejście jest znacznie bardziej intuicyjne niż inna odpowiedź i oznaczałoby to poprawną odpowiedź. Zaimplementowałem to w mojej aplikacji piaskownicy bez żadnych problemów.
BLSully,
4
Prawdopodobnie powinieneś sprawdzić, aby upewnić się, że scope.controlistnieje, w przeciwnym razie inne miejsca, które korzystają z dyrektywy, ale nie muszą uzyskiwać dostępu do metod dyrektywy i nie mają controlattr, zaczną undefined
zgłaszać
73

Zakładając, że przycisk działania używa tego samego kontrolera $scopejako dyrektywy, wystarczy zdefiniować funkcję updateMapna $scopewewnątrz funkcji łącza. Kontroler może wtedy wywołać tę funkcję po kliknięciu przycisku akcji.

<div ng-controller="MyCtrl">
    <map></map>
    <button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {
            $scope.updateMap = function() {
                alert('inside updateMap()');
            }
        }
    }
});

fiddle


Zgodnie z komentarzem @ FlorianF, jeśli dyrektywa używa izolowanego zakresu, sprawy są bardziej skomplikowane. Oto jeden ze sposobów, aby działał: dodaj set-fnatrybut do mapdyrektywy, który zarejestruje funkcję dyrektywy w kontrolerze:

<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope: { setFn: '&' },
link: function(scope, element, attrs) {
    scope.updateMap = function() {
       alert('inside updateMap()');
    }
    scope.setFn({theDirFn: scope.updateMap});
}
function MyCtrl($scope) {
    $scope.setDirectiveFn = function(directiveFn) {
        $scope.directiveFn = directiveFn;
    };
}

fiddle

Mark Rajcok
źródło
Co jeśli dyrektywa ma izolowany zakres?
Florian F
Dzięki! (Być może łatwiej byłoby wywołać funkcję zdefiniowaną w kontrolerze dyrektywy, ale nie jestem tego pewien)
Florian F
1
Jest to znacznie lepszy sposób, jeśli nie masz do czynienia z izolowanym lunetą.
Martin Frank
Ta odpowiedź faktycznie odpowiada na pytanie OP. Używa również zakresu izolowanego, aby uzyskać zakres izolowany, wystarczy dodać scopewłaściwość do deklaracji dyrektywy.
Daniel G.,
35

Chociaż może być kuszące, aby ujawnić obiekt na izolowanym zakresie dyrektywy, aby ułatwić komunikację z nim, działanie może prowadzić do mylącego kodu „spaghetti”, szczególnie jeśli trzeba połączyć tę komunikację na kilka poziomów (kontroler, dyrektywa, do dyrektywy zagnieżdżonej itp.)

Początkowo podążaliśmy tą ścieżką, ale po kilku dalszych badaniach okazało się, że ma to większy sens i zaowocowało zarówno łatwiejszym w utrzymaniu, jak i czytelnym kodem do ujawnienia zdarzeń i właściwości, których dyrektywa użyje do komunikacji za pośrednictwem usługi, a następnie za pomocą $ watch we właściwościach tej usługi w dyrektywa lub wszelkie inne mechanizmy kontrolne, które musiałyby reagować na te zmiany w zakresie komunikacji.

Ta abstrakcja działa bardzo dobrze z ramką wstrzykiwania zależności AngularJS, ponieważ można wstrzyknąć usługę do dowolnych elementów, które muszą zareagować na te zdarzenia. Jeśli spojrzysz na plik Angular.js, zobaczysz, że tam zawarte dyrektywy również używają usług i $ watch w ten sposób, nie ujawniają zdarzeń poza izolowanym zakresem.

Na koniec, w przypadku, gdy trzeba komunikować się między dyrektywami, które są od siebie zależne, zaleciłbym współużytkowanie kontrolera między tymi dyrektywami jako środka komunikacji.

Wiki AngularJS dla najlepszych praktyk wspomina również o tym:

Używaj tylko. $ Broadcast (),. $ Emit () i. $ On () dla zdarzeń atomowych Zdarzenia, które są istotne globalnie w całej aplikacji (takie jak uwierzytelnianie użytkownika lub zamykanie aplikacji). Jeśli chcesz wydarzeń specyficznych dla modułów, usług lub widżetów, powinieneś rozważyć Usługi, Kontrolery Dyrektyw lub Biblioteki stron trzecich

  • $ scope. $ watch () powinien zastąpić potrzebę zdarzeń
  • Bezpośrednie wstrzykiwanie usług i metod wywoływania jest również przydatne do bezpośredniej komunikacji
  • Dyrektywy mogą bezpośrednio komunikować się ze sobą za pośrednictwem kontrolerów dyrektyw
Zawsze się uczę
źródło
2
Dotarłem intuicyjnie do dwóch rozwiązań: (1) obserwuj zmianę zmiennej zasięgu =, która zawiera nazwę metody i argumenty. (2) ujawnij ciąg powiązania jednokierunkowego @jako identyfikator tematu i pozwól odbiorcy wysłać zdarzenie dotyczące tego tematu. Teraz zobaczyłem wiki najlepszych praktyk. Myślę, że istnieje powód, aby tego nie robić w jakiś sposób. Ale nadal nie jestem bardzo jasne, jak to działa. W moim przypadku utworzyłem dyrektywę dotyczącą tabel, chcę ujawnić switchTab(tabIndex)metodę. Czy możesz podać więcej przykładów?
stanleyxu2005
Nie ujawniłbyś switchTab(tabIndex)metody, wiązałbyś się tylko ze tabIndexzmienną. Kontroler strony może mieć działania, które zmieniają tę zmienną. Wiążesz / przekazujesz tę zmienną do swojej zakładki Dyrektywa. Dyrektywa zakładki może następnie obserwować tę zmienną pod kątem zmian i samodzielnie wykonać switchTab. Ponieważ dyrektywa decyduje, kiedy / jak kontrolować swoje zakładki na podstawie zmiennej. To nie jest zadanie zewnętrznego źródła, w przeciwnym razie źródła zewnętrzne wymagają znajomości wewnętrznych zasad funkcjonowania dyrektywy, co jest złe.
Suamere,
15

Opierając się na odpowiedzi Olivera - nie zawsze możesz potrzebować dostępu do wewnętrznych metod dyrektywy, a w takich przypadkach prawdopodobnie nie chcesz tworzyć pustego obiektu i dodawać controlatrybutu do dyrektywy, aby zapobiec zgłoszeniu błędu ( cannot set property 'takeTablet' of undefined).

Możesz także chcieć użyć tej metody w innych miejscach dyrektywy.

Dodałbym sprawdzenie, aby upewnić się, że scope.controlistnieje, i ustawiłem dla niego metody w podobny sposób, jak wzór modułu ujawniającego

app.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{control}}</div>',
    scope: {
      control: '='
    },
    link : function (scope, element, attrs) {
      var takenTablets = 0;
      var takeTablet = function() {
        takenTablets += 1;  
      }

      if (scope.control) {
        scope.control = {
          takeTablet: takeTablet
        };
      }
    }
  };
});
CheapSteaks
źródło
od razu, zastosowanie odkrywczego wzoru w dyrektywie sprawia, że ​​intencje są znacznie bardziej wyraźne. niezłe!
JSancho
12

Szczerze mówiąc, tak naprawdę nie byłem przekonany do żadnej odpowiedzi w tym wątku. Oto moje rozwiązania:

Podejście do obsługi dyrektywy (menedżera)

Ta metoda jest niezależna od tego, czy dyrektywa $scopejest wspólna, czy odizolowana

A, factoryaby zarejestrować instancje dyrektywy

angular.module('myModule').factory('MyDirectiveHandler', function() {
    var instance_map = {};
    var service = {
        registerDirective: registerDirective,
        getDirective: getDirective,
        deregisterDirective: deregisterDirective
    };

    return service;

    function registerDirective(name, ctrl) {
        instance_map[name] = ctrl;
    }

    function getDirective(name) {
        return instance_map[name];
    }

    function deregisterDirective(name) {
        instance_map[name] = null;
    }
});

Kod dyrektywy, zwykle umieszczam całą logikę, która nie dotyczy DOM, w kontrolerze dyrektywy. I zarejestrowanie instancji kontrolera w naszym module obsługi

angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {
    var directive = {
        link: link,
        controller: controller
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        var name = $attrs.name;

        this.updateMap = function() {
            //some code
        };

        MyDirectiveHandler.registerDirective(name, this);

        $scope.$on('destroy', function() {
            MyDirectiveHandler.deregisterDirective(name);
        });
    }
})

kod szablonu

<div my-directive name="foo"></div>

Uzyskaj dostęp do instancji kontrolera za pomocą factory& uruchom publicznie udostępnione metody

angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {
    $scope.someFn = function() {
        MyDirectiveHandler.get('foo').updateMap();
    };
});

Podejście Angulara

Wyciągając liść z książki Angulara o tym, jak sobie radzą

<form name="my_form"></form>

przy użyciu $ parsowania i rejestracji kontrolera na $parentzakresie. Ta technika nie działa na pojedyncze $scopedyrektywy.

angular.module('myModule').directive('myDirective', function($parse) {
    var directive = {
        link: link,
        controller: controller,
        scope: true
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        $parse($attrs.name).assign($scope.$parent, this);

        this.updateMap = function() {
            //some code
        };
    }
})

Uzyskaj dostęp do kontrolera za pomocą $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});
Mudassir Ali
źródło
„Podejście Angulara” wygląda świetnie! Jest jednak literówka: $scope.foopowinno być$scope.my_form
Daniel D
Nah, to byłoby $scope.fooponieważ nasz szablon jest <div my-directive name="foo"></div>i namewartość atrybutu jest „foo”. <formjest tylko przykładem jednej z dyrektyw kątowych, która stosuje tę technikę
Mudassir Ali
10

Trochę późno, ale jest to rozwiązanie z izolowanym zakresem i „zdarzeniami” do wywołania funkcji w dyrektywie. To rozwiązanie jest inspirowane tym postem SO autorstwa satchmorun i dodaje moduł oraz interfejs API.

//Create module
var MapModule = angular.module('MapModule', []);

//Load dependency dynamically
angular.module('app').requires.push('MapModule');

Utwórz interfejs API do komunikacji z dyrektywą. AddUpdateEvent dodaje zdarzenie do tablicy zdarzeń, a updateMap wywołuje każdą funkcję zdarzenia.

MapModule.factory('MapApi', function () {
    return {
        events: [],

        addUpdateEvent: function (func) {
            this.events.push(func);
        },

        updateMap: function () {
            this.events.forEach(function (func) {
                func.call();
            });
        }
    }
});

(Być może musisz dodać funkcjonalność, aby usunąć zdarzenie).

W dyrektywie ustaw odwołanie do MapAPI i dodaj $ scope.updateMap jako zdarzenie po wywołaniu MapApi.updateMap.

app.directive('map', function () {
    return {
        restrict: 'E', 
        scope: {}, 
        templateUrl: '....',
        controller: function ($scope, $http, $attrs, MapApi) {

            $scope.api = MapApi;

            $scope.updateMap = function () {
                //Update the map 
            };

            //Add event
            $scope.api.addUpdateEvent($scope.updateMap);

        }
    }
});

W „głównym” kontrolerze dodaj odwołanie do MapApi i po prostu wywołaj MapApi.updateMap (), aby zaktualizować mapę.

app.controller('mainController', function ($scope, MapApi) {

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}
AxdorphCoder
źródło
2
Ta propozycja wymagałaby nieco więcej pracy w prawdziwym świecie, jeśli masz wiele dyrektyw tego samego typu, w zależności od usługi interfejsu API. Z pewnością znajdziesz się w sytuacji, w której musisz celować i wywoływać funkcje tylko z jednej konkretnej dyrektywy, a nie ze wszystkich. Czy chcesz ulepszyć swoją odpowiedź dzięki rozwiązaniu tego problemu?
smajl
5

Możesz określić atrybut DOM, którego można użyć, aby umożliwić dyrektywie zdefiniowanie funkcji w zakresie nadrzędnym. Zakres nadrzędny może następnie wywołać tę metodę jak każdą inną. Oto plunker. A poniżej znajduje się odpowiedni kod.

clearfn jest atrybutem elementu dyrektywy, do którego zakres nadrzędny może przekazać właściwość zakresu, którą dyrektywa może następnie ustawić na funkcję spełniającą pożądane zachowanie.

<!DOCTYPE html>
<html ng-app="myapp">
  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <style>
      my-box{
        display:block;
        border:solid 1px #aaa;
        min-width:50px;
        min-height:50px;
        padding:.5em;
        margin:1em;
        outline:0px;
        box-shadow:inset 0px 0px .4em #aaa;
      }
    </style>
  </head>
  <body ng-controller="mycontroller">
    <h1>Call method on directive</h1>
    <button ng-click="clear()">Clear</button>
    <my-box clearfn="clear" contentEditable=true></my-box>
    <script>
      var app = angular.module('myapp', []);
      app.controller('mycontroller', function($scope){
      });
      app.directive('myBox', function(){
        return {
          restrict: 'E',
          scope: {
            clearFn: '=clearfn'
          },
          template: '',
          link: function(scope, element, attrs){
            element.html('Hello World!');
            scope.clearFn = function(){
              element.html('');
            };
          }
        }
      });
    </script>
  </body>
</html>
Trevor
źródło
Nie rozumiem, dlaczego to działa .. czy to dlatego, że czysty atrybut jest w jakimś zakresie?
Quinn Wilson,
1
Staje się częścią zakresu dyrektywy, gdy tylko ją zadeklarujesz (np scope: { clearFn: '=clearfn' }.).
Trevor,
2

Wystarczy użyć scope. $ Parent, aby powiązać funkcję wywoływaną z funkcją dyrektywy

angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) {

}])
.directive('mydirective',function(){
 function link(scope, el, attr){
   //use scope.$parent to associate the function called to directive function
   scope.$parent.myfunction = function directivefunction(parameter){
     //do something
}
}
return {
        link: link,
        restrict: 'E'   
      };
});

w HTML

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>
ramon prata
źródło
2

Możesz podać nazwę metody do dyrektywy, aby zdefiniować, który chcesz wywołać ze sterownika, ale bez izolowania zakresu,

angular.module("app", [])
  .directive("palyer", [
    function() {
      return {
        restrict: "A",
        template:'<div class="player"><span ng-bind="text"></span></div>',
        link: function($scope, element, attr) {
          if (attr.toPlay) {
            $scope[attr.toPlay] = function(name) {
              $scope.text = name + " playing...";
            }
          }
        }
      };
    }
  ])
  .controller("playerController", ["$scope",
    function($scope) {
      $scope.clickPlay = function() {
        $scope.play('AR Song');
      };
    }
  ]);
.player{
  border:1px solid;
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="playerController">
    <p>Click play button to play
      <p>
        <p palyer="" to-play="play"></p>
        <button ng-click="clickPlay()">Play</button>

  </div>
</div>

Naveen raj
źródło
1

TESTOWANE Mam nadzieję, że to komuś pomoże.

Moje proste podejście (myśl tagi jako swój oryginalny kod)

<html>
<div ng-click="myfuncion"> 
<my-dir callfunction="myfunction">
</html>

<directive "my-dir">
callfunction:"=callfunction"
link : function(scope,element,attr) {
scope.callfunction = function() {
 /// your code
}
}
</directive>
Santosh Kumar
źródło
0

Być może nie jest to najlepszy wybór, ale możesz zrobić angular.element("#element").isolateScope()lub $("#element").isolateScope()uzyskać dostęp do zakresu i / lub kontrolera swojej dyrektywy.

Alex198710
źródło
0

Jak uzyskać kontroler dyrektywy w kontrolerze strony:

  1. napisz niestandardową dyrektywę, aby uzyskać odwołanie do kontrolera dyrektywy z elementu DOM:

    angular.module('myApp')
        .directive('controller', controller);
    
    controller.$inject = ['$parse'];
    
    function controller($parse) {
        var directive = {
            restrict: 'A',
            link: linkFunction
        };
        return directive;
    
        function linkFunction(scope, el, attrs) {
            var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
            var directiveController = el.controller(directiveName);
    
            var model = $parse(attrs.controller);
            model.assign(scope, directiveController);
        }
    }
  2. użyj go w html kontrolera strony:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. Użyj kontrolera dyrektywy w kontrolerze strony:

    vm.myDirectiveController.callSomeMethod();

Uwaga: dane rozwiązanie działa tylko dla kontrolerów dyrektyw elementowych (nazwa znacznika służy do uzyskania nazwy żądanej dyrektywy).

Robert J.
źródło
0

Poniższe rozwiązanie przyda się, gdy masz kontrolery (nadrzędne i dyrektywy (izolowane)) w formacie „kontrolera jako”

ktoś może uznać to za przydatne,

dyrektywa:

var directive = {
        link: link,
        restrict: 'E',
        replace: true,
        scope: {
            clearFilters: '='
        },
        templateUrl: "/temp.html",
        bindToController: true, 
        controller: ProjectCustomAttributesController,
        controllerAs: 'vmd'
    };
    return directive;

    function link(scope, element, attrs) {
        scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
    }
}

kontroler dyrektywy:

function DirectiveController($location, dbConnection, uiUtility) {
  vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;

function SetFitlersToDefaultValue() {
           //your logic
        }
}

Kod HTML :

      <Test-directive clear-filters="vm.ClearFilters"></Test-directive>
    <a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a> 
//this button is from parent controller which will call directive controller function
Raunak Mali
źródło