Wie rufe ich eine in einer AngularJS-Direktive definierte Methode auf?

297

Ich habe eine Anweisung, hier ist der Code:

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

Ich möchte updateMap()eine Benutzeraktion aufrufen . Die Aktionsschaltfläche befindet sich nicht in der Direktive.

Was ist der beste Weg, um updateMap()von einem Controller aus anzurufen ?

mcbjam
quelle
11
Kleine Randnotiz: Die Konvention besteht darin, das Dollarzeichen für 'scope' in einer Link-Funktion nicht zu verwenden, da der Bereich nicht eingefügt, sondern als reguläres Argument übergeben wird.
Noam

Antworten:

369

Wenn Sie isolierte Bereiche verwenden möchten, können Sie ein Steuerobjekt übergeben, indem Sie =eine Variable aus dem Controller-Bereich bidirektional binden . Sie können auch mehrere Instanzen derselben Direktive auf einer Seite mit demselben Steuerobjekt steuern.

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
quelle
11
+1 Auf diese Weise erstelle ich auch APIs für meine wiederverwendbaren Komponenten in Angular.
Romiem
5
Dies ist sauberer als die akzeptierte Antwort und +1 für die Simpsons-Referenz, wenn ich mich nicht irre
Blake Miller
44
Genau so habe ich das gleiche Problem gelöst. Es funktioniert, aber es sieht aus wie ein Hack ... Ich wünschte, Angular hätte eine bessere Lösung dafür.
Dema
1
Ich lerne eckig, daher hat meine Meinung vielleicht nicht viel Gewicht, aber ich fand diesen Ansatz viel intuitiver als die andere Antwort und hätte ihn als die richtige Antwort markiert. Ich habe dies problemlos in meine Sandbox-Anwendung implementiert.
BLSully
4
Sie sollten wahrscheinlich eine Überprüfung durchführen, um sicherzustellen scope.control, dass andere Stellen, die die Direktive verwenden, aber nicht auf die Methoden der Direktive zugreifen müssen und keine controlattr haben, Fehler auslösen, wenn sie keine Attribute undefined
festlegen können
73

Unter der Annahme , daß die Aktionstaste verwendet den gleichen Controller $scopeals Richtlinie definiert gerade Funktion updateMapauf $scopeinnerhalb der Link - Funktion. Ihr Controller kann diese Funktion dann aufrufen, wenn Sie auf die Aktionsschaltfläche klicken.

<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


Laut dem Kommentar von @ FlorianF sind die Dinge komplizierter, wenn die Richtlinie einen isolierten Geltungsbereich verwendet. Hier ist eine Möglichkeit, wie es funktioniert: Fügen Sie set-fnder mapDirektive ein Attribut hinzu, das die Direktivenfunktion beim Controller registriert:

<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
quelle
Was ist, wenn die Richtlinie einen isolierten Geltungsbereich hat?
Florian F
Vielen Dank! (Vielleicht wäre es einfacher, eine im Controller der Direktive definierte Funktion aufzurufen, aber da bin ich mir nicht sicher)
Florian F
1
Dies ist viel besser, wenn Sie nicht mit einem isolierten Bereich arbeiten.
Martin Frank
Diese Antwort beantwortet tatsächlich die OP-Frage. Es wird auch ein isolierter Bereich verwendet. Um einen isolierten Bereich zu haben, müssen Sie nur die scopeEigenschaft zur Direktivendeklaration hinzufügen .
Daniel G.
35

Obwohl es möglicherweise verlockend ist, ein Objekt dem isolierten Geltungsbereich einer Direktive auszusetzen, um die Kommunikation mit ihr zu erleichtern, kann dies zu verwirrendem "Spaghetti" -Code führen, insbesondere wenn Sie diese Kommunikation über mehrere Ebenen verketten müssen (Controller, Direktive, zu verschachtelten Direktiven usw.)

Wir sind diesen Weg ursprünglich gegangen, aber nach einigen weiteren Untersuchungen stellte sich heraus, dass dies sinnvoller war und sowohl zu wartbarem als auch zu lesbarem Code führte, um Ereignisse und Eigenschaften verfügbar zu machen, die eine Direktive für die Kommunikation über einen Dienst verwendet, und dann $ watch für die Eigenschaften dieses Dienstes in zu verwenden die Richtlinie oder andere Kontrollen, die auf diese Änderungen für die Kommunikation reagieren müssten.

Diese Abstraktion funktioniert sehr gut mit AngularJSs Framework für die Abhängigkeitsinjektion, da Sie den Service in alle Elemente einfügen können, die auf diese Ereignisse reagieren müssen. Wenn Sie sich die Datei Angular.js ansehen, werden Sie feststellen, dass die dortigen Anweisungen auch Dienste und $ watch auf diese Weise verwenden. Sie machen keine Ereignisse über den isolierten Bereich verfügbar.

Für den Fall, dass Sie zwischen voneinander abhängigen Direktiven kommunizieren müssen, würde ich empfehlen, einen Controller zwischen diesen Direktiven als Kommunikationsmittel zu verwenden.

AngularJSs Wiki for Best Practices erwähnt dies auch:

Verwenden Sie. $ Broadcast (),. $ Emit () und. $ On () nur für atomare Ereignisse. Ereignisse, die global für die gesamte App relevant sind (z. B. Benutzerauthentifizierung oder Schließen der App). Wenn Sie Ereignisse wünschen, die für Module, Dienste oder Widgets spezifisch sind, sollten Sie Dienste, Richtliniencontroller oder Bibliotheken von Drittanbietern in Betracht ziehen

  • $ scope. $ watch () sollte die Notwendigkeit von Ereignissen ersetzen
  • Das direkte Injizieren von Diensten und Aufrufen von Methoden ist auch für die direkte Kommunikation nützlich
  • Direktiven können über Direktiven-Controller direkt miteinander kommunizieren
Immer lernen
quelle
2
Ich habe intuitiv zwei Lösungen gefunden: (1) Beobachten Sie die Änderung einer Bereichsvariablen =. Die Variable enthält den Methodennamen und die Argumente. (2) Stellen Sie eine Einweg-Bindungszeichenfolge @als Themen-ID bereit und lassen Sie den Angerufenen ein Ereignis zu diesem Thema senden. Jetzt habe ich das Best Practice Wiki gesehen. Ich denke, es gibt Grund, es nicht im Mai zu tun. Aber ich bin mir immer noch nicht ganz sicher, wie es funktioniert. In meinem Fall habe ich eine Tabset-Direktive erstellt, ich möchte eine switchTab(tabIndex)Methode verfügbar machen . Könnten Sie mehr Beispiel?
Stanleyxu2005
Sie würden keine switchTab(tabIndex)Methode verfügbar machen, sondern nur an eine tabIndexVariable binden . Ihr Seitencontroller verfügt möglicherweise über Aktionen, die diese Variable ändern. Sie binden / übergeben diese Variable an Ihre Registerkarte Direktive. Ihre Tab-Direktive kann diese Variable dann auf Änderungen überwachen und switchTab von selbst ausführen. Weil die Direktive anhand einer Variablen entscheidet, wann / wie ihre Registerkarten gesteuert werden sollen. Das ist nicht die Aufgabe einer externen Quelle, sonst erfordern die externen Quellen Kenntnisse über das Innenleben der Richtlinie, was schlecht ist.
Suamere
15

Aufbauend auf Olivers Antwort - Sie müssen möglicherweise nicht immer auf die inneren Methoden einer Direktive zugreifen, und in diesen Fällen möchten Sie wahrscheinlich kein leeres Objekt erstellen und controlder Direktive ein attr hinzufügen müssen, um zu verhindern, dass sie einen Fehler auslöst (cannot set property 'takeTablet' of undefined ).

Möglicherweise möchten Sie die Methode auch an anderen Stellen innerhalb der Direktive verwenden.

Ich würde eine Prüfung hinzufügen, um sicherzustellen, dass sie scope.controlvorhanden ist, und Methoden dafür festlegen, die dem aufschlussreichen Modulmuster ähneln

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
quelle
Die Verwendung eines aufschlussreichen Musters innerhalb der Richtlinie macht die Absichten viel klarer. Schön!
JSancho
12

Um ehrlich zu sein, war ich von keiner der Antworten in diesem Thread wirklich überzeugt. Hier sind meine Lösungen:

Ansatz des Direktivenhandlers (Managers)

Diese Methode ist unabhängig davon, ob die Richtlinie $scope um eine gemeinsame oder eine isolierte handelt

A factory, um die Instanzen der Direktive zu registrieren

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

Beim Direktivencode habe ich normalerweise die gesamte Logik, die sich nicht mit DOM befasst, in den Direktiven-Controller eingefügt. Und Registrieren der Controller-Instanz in unserem Handler

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

Vorlagencode

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

Greifen Sie mit factoryden öffentlich verfügbaren Methoden auf die Controller-Instanz zu

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

Angulars Ansatz

Nehmen Sie ein Blatt aus Angulars Buch darüber, wie sie damit umgehen

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

Verwenden von $ parse und Registrieren des Controllers für den $parentBereich. Diese Technik funktioniert nicht bei isolierten $scopeAnweisungen.

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

Greifen Sie mit dem Controller darauf zu $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});
Mudassir Ali
quelle
"Angulars Ansatz" sieht gut aus! Es gibt jedoch einen Tippfehler: $scope.foosollte sein$scope.my_form
Daniel D
Nein, es wäre, $scope.fooda unsere Vorlage ist <div my-directive name="foo"></div>und nameder Wert des Attributs 'foo' ist. <formist nur ein Beispiel für eine der Winkelanweisungen, die diese Technik anwenden
Mudassir Ali
10

Ein bisschen spät, aber dies ist eine Lösung mit dem isolierten Bereich und "Ereignissen", um eine Funktion in der Direktive aufzurufen. Diese Lösung ist von diesem SO-Beitrag von satchmorun inspiriert und fügt ein Modul und eine API hinzu.

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

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

Erstellen Sie eine API für die Kommunikation mit der Direktive. Das addUpdateEvent fügt dem Ereignisarray ein Ereignis hinzu und updateMap ruft jede Ereignisfunktion auf.

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

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

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

(Möglicherweise müssen Sie Funktionen hinzufügen, um das Ereignis zu entfernen.)

Legen Sie in der Direktive einen Verweis auf die MapAPI fest und fügen Sie $ scope.updateMap als Ereignis hinzu, wenn MapApi.updateMap aufgerufen wird.

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

        }
    }
});

Fügen Sie im "Haupt" -Controller einen Verweis auf MapApi hinzu und rufen Sie einfach MapApi.updateMap () auf, um die Map zu aktualisieren.

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

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}
AxdorphCoder
quelle
2
Dieser Vorschlag würde in einer realen Welt etwas mehr Arbeit erfordern, wenn Sie abhängig von Ihrem API-Dienst mehrere Direktiven desselben Typs haben. Sie werden sicher in eine Situation geraten, in der Sie Funktionen nur von einer bestimmten Direktive und nicht von allen aus zielen und aufrufen müssen. Möchten Sie Ihre Antwort mit einer Lösung für dieses Problem erweitern?
Smajl
5

Sie können ein DOM-Attribut angeben, mit dem die Direktive eine Funktion für den übergeordneten Bereich definieren kann. Der übergeordnete Bereich kann diese Methode dann wie jede andere aufrufen. Hier ist ein Plunker. Und unten ist der relevante Code.

clearfn ist ein Attribut für das Direktivenelement, an das der übergeordnete Bereich eine Bereichseigenschaft übergeben kann, die die Direktive dann auf eine Funktion setzen kann, die das gewünschte Verhalten erreicht.

<!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
quelle
Ich verstehe nicht, warum das funktioniert. Liegt es daran, dass das eindeutige Attribut in gewisser Weise im Geltungsbereich liegt?
Quinn Wilson
1
Es wird Teil des Geltungsbereichs der Richtlinie, sobald Sie es deklarieren (z scope: { clearFn: '=clearfn' }. B. ).
Trevor
2

Verwenden Sie einfach scope. $ Parent, um die Funktion der Direktivenfunktion zuzuordnen

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

in HTML

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>
ramon prata
quelle
2

Sie können den Methodennamen der Direktive mitteilen, um zu definieren, welche vom Controller aufgerufen werden soll, ohne jedoch den Bereich zu isolieren.

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

GEPRÜFT Hoffe das hilft jemandem.

Mein einfacher Ansatz (Think Tags als Originalcode)

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

Vielleicht ist dies nicht die beste Wahl, aber Sie können den Geltungsbereich und / oder den Controller Ihrer Direktive verwenden angular.element("#element").isolateScope()oder $("#element").isolateScope()darauf zugreifen.

Alex198710
quelle
0

So erhalten Sie den Controller einer Direktive in einen Seitencontroller:

  1. Schreiben Sie eine benutzerdefinierte Direktive, um den Verweis auf den Direktiven-Controller vom DOM-Element abzurufen:

    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. Verwenden Sie es im HTML-Code des Seitencontrollers:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. Verwenden Sie den Direktiven-Controller im Seiten-Controller:

    vm.myDirectiveController.callSomeMethod();

Hinweis: Die angegebene Lösung funktioniert nur für Controller von Elementanweisungen (der Tag-Name wird verwendet, um den Namen der gewünschten Anweisung abzurufen).

Robert J.
quelle
0

Die folgende Lösung ist hilfreich, wenn Sie Controller (sowohl übergeordnete als auch Direktive (isoliert)) im Format "Controller As" haben

jemand könnte dies nützlich finden,

Richtlinie:

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

Direktive Controller:

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

function SetFitlersToDefaultValue() {
           //your logic
        }
}

HTML Quelltext :

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