Aufruf der Methode in der Direktivensteuerung von einer anderen Steuerung

118

Ich habe eine Direktive, die einen eigenen Controller hat. Siehe den folgenden Code:

var popdown = angular.module('xModules',[]);

popdown.directive('popdown', function () {
    var PopdownController = function ($scope) {
        this.scope = $scope;
    }

    PopdownController.prototype = {
        show:function (message, type) {
            this.scope.message = message;
            this.scope.type = type;
        },

        hide:function () {
            this.scope.message = '';
            this.scope.type = '';
        }
    }

    var linkFn = function (scope, lElement, attrs, controller) {

    };

    return {
        controller: PopdownController,
        link: linkFn,
        replace: true,
        templateUrl: './partials/modules/popdown.html'
    }

});

Dies soll ein Benachrichtigungssystem für Fehler / Benachrichtigungen / Warnungen sein. Ich möchte von einer anderen Steuerung (keine Direktive) die Funktion showauf dieser Steuerung aufrufen . Und wenn ich das mache, möchte ich auch, dass meine Link-Funktion erkennt, dass sich einige Eigenschaften geändert haben, und einige Animationen ausführt.

Hier ist ein Code, der veranschaulicht, wonach ich frage:

var app = angular.module('app', ['RestService']);

app.controller('IndexController', function($scope, RestService) {
    var result = RestService.query();

    if(result.error) {
        popdown.notify(error.message, 'error');
    }
});

Wenn Sie also showden popdownDirektiven-Controller aufrufen , sollte auch die Link-Funktion ausgelöst werden und eine Animation ausführen. Wie könnte ich das erreichen?

user253530
quelle
Wo rufen Sie die popdownDirektive auf der Seite auf - befindet sich nur an einer Stelle, an der alle anderen Controller Zugriff darauf haben sollen, oder gibt es mehrere Popdowns an verschiedenen Stellen?
Satchmorun
Meine index.html hat Folgendes: <div ng-view> </ div> <div popdown> </ div> Grundsätzlich gibt es nur eine Popdown-Instanz, die global verfügbar sein soll.
user253530
1
Ich denke du wolltest schreiben popdown.show(...)anstatt popdown.notify(...)ist das richtig? Ansonsten ist die Benachrichtigungsfunktion etwas verwirrend.
Lanoxx
wo kommt es her popdown.notify? .notifiyMethode, ich meine
Green

Antworten:

167

Dies ist eine interessante Frage, und ich begann darüber nachzudenken, wie ich so etwas implementieren würde.

Ich habe mir das ausgedacht (Geige) ;

Anstatt zu versuchen, eine Direktive von einem Controller aufzurufen, habe ich ein Modul erstellt, in dem die gesamte Popdown-Logik untergebracht ist:

var PopdownModule = angular.module('Popdown', []);

Ich habe zwei Dinge in das Modul eingefügt , eine factoryfür die API, die überall directiveeingefügt werden kann, und die für die Definition des Verhaltens des tatsächlichen Popdown-Elements:

Die Fabrik definiert nur ein paar Funktionen successund errorund verfolgt die Spuren von ein paar Variablen:

PopdownModule.factory('PopdownAPI', function() {
    return {
        status: null,
        message: null,
        success: function(msg) {
            this.status = 'success';
            this.message = msg;
        },
        error: function(msg) {
            this.status = 'error';
            this.message = msg;
        },
        clear: function() {
            this.status = null;
            this.message = null;
        }
    }
});

Die Direktive ruft die API in ihren Controller ein und überwacht die API auf Änderungen (ich verwende zur Vereinfachung Bootstrap-CSS):

PopdownModule.directive('popdown', function() {
    return {
        restrict: 'E',
        scope: {},
        replace: true,
        controller: function($scope, PopdownAPI) {
            $scope.show = false;
            $scope.api = PopdownAPI;

            $scope.$watch('api.status', toggledisplay)
            $scope.$watch('api.message', toggledisplay)

            $scope.hide = function() {
                $scope.show = false;
                $scope.api.clear();
            };

            function toggledisplay() {
                $scope.show = !!($scope.api.status && $scope.api.message);               
            }
        },
        template: '<div class="alert alert-{{api.status}}" ng-show="show">' +
                  '  <button type="button" class="close" ng-click="hide()">&times;</button>' +
                  '  {{api.message}}' +
                  '</div>'
    }
})

Dann definiere ich ein appModul, das abhängt von Popdown:

var app = angular.module('app', ['Popdown']);

app.controller('main', function($scope, PopdownAPI) {
    $scope.success = function(msg) { PopdownAPI.success(msg); }
    $scope.error   = function(msg) { PopdownAPI.error(msg); }
});

Und der HTML sieht aus wie:

<html ng-app="app">
    <body ng-controller="main">
        <popdown></popdown>
        <a class="btn" ng-click="success('I am a success!')">Succeed</a>
        <a class="btn" ng-click="error('Alas, I am a failure!')">Fail</a>
    </body>
</html>

Ich bin mir nicht sicher, ob es völlig ideal ist, aber es schien ein vernünftiger Weg zu sein, die Kommunikation mit einer globalen Popdown-Direktive einzurichten.

Wieder als Referenz die Geige .

satchmorun
quelle
10
+1 Man sollte niemals eine Funktion in einer Direktive von außerhalb der Direktive aufrufen - es ist eine schlechte Praxis. Die Verwendung eines Dienstes zum Verwalten des globalen Status, den eine Direktive liest, ist sehr verbreitet, und dies ist der richtige Ansatz. Weitere Anwendungen umfassen Benachrichtigungswarteschlangen und modale Dialoge.
Josh David Miller
7
Wirklich außergewöhnliche Antwort! Ein so nützliches Beispiel für diejenigen von uns, die von jQuery und Backbone kommen
Brandon
11
Ist es auf diese Weise möglich, mit diesem Modul mehrere Direktiven in derselben Ansicht zu instanziieren? Wie kann ich die Erfolgs- oder Fehlerfunktion einer bestimmten Instanz dieser Direktive aufrufen?
ira
3
@ira Sie könnten wahrscheinlich die Fabrik ändern, um eine Karte (oder Liste) von Status- und Nachrichtenobjekten zu führen, und dann ein Namensattribut in der Direktive verwenden, um zu identifizieren, welches Element in der Liste Sie benötigen. Anstatt success(msg)das HTML aufzurufen sucess(name, msg), würden Sie aufrufen , um die Direktive mit dem richtigen Namen auszuwählen.
Lanoxx
5
@JoshDavidMiller Warum halten Sie es für eine schlechte Praxis, eine Methode für eine Direktive aufzurufen? Wenn eine Direktive die DOM-Logik wie beabsichtigt kapselt, ist es sicherlich ganz natürlich, eine API verfügbar zu machen, damit Controller, die sie verwenden, ihre Methoden nach Bedarf aufrufen können.
Paul Taylor
27

Sie können auch Ereignisse verwenden, um den Popdown auszulösen.

Hier ist eine Geige, die auf der Lösung von Satchmorun basiert. Es verzichtet auf die PopdownAPI und die Controller der obersten Ebene stattdessen auf $broadcastdie Ereignisse "Erfolg" und "Fehler" in der gesamten Bereichskette:

$scope.success = function(msg) { $scope.$broadcast('success', msg); };
$scope.error   = function(msg) { $scope.$broadcast('error', msg); };

Das Popdown-Modul registriert dann Handlerfunktionen für diese Ereignisse, z.

$scope.$on('success', function(event, msg) {
    $scope.status = 'success';
    $scope.message = msg;
    $scope.toggleDisplay();
});

Dies funktioniert zumindest und scheint mir eine gut entkoppelte Lösung zu sein. Ich werde andere einschalten lassen, wenn dies aus irgendeinem Grund als schlechte Praxis angesehen wird.

Aron
quelle
1
Ein Nachteil, den ich mir vorstellen kann, ist, dass Sie in der ausgewählten Antwort nur die PopdownAPI benötigen (leicht verfügbar mit DI). In diesem Fall benötigen Sie Zugriff auf den Bereich des Controllers, um die Nachricht zu senden. Wie auch immer, es sieht sehr prägnant aus.
Julian
Ich mag dies besser als den Service-Ansatz für einfache Anwendungsfälle, da er die Komplexität gering hält und immer noch lose gekoppelt ist
Patrick Favre
11

Sie können den Controller der Direktive auch dem übergeordneten Bereich aussetzen, wie dies ngFormbei nameAttributen der Fall ist: http://docs.angularjs.org/api/ng.directive:ngForm

Hier finden Sie ein sehr einfaches Beispiel, wie dies erreicht werden kann: http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

In diesem Beispiel habe ich myDirectivemit dediziertem Controller mit $clearMethode (eine Art sehr einfache öffentliche API für die Direktive). Ich kann diesen Controller im übergeordneten Bereich veröffentlichen und diese Methode außerhalb der Direktive aufrufen.

Luacassus
quelle
Dies erfordert eine Beziehung zwischen den Controllern, oder? Da OP ein Nachrichtenzentrum wollte, ist dies für ihn möglicherweise nicht ideal. Aber es war auch sehr schön, Ihren Ansatz zu lernen. Es ist in vielen Situationen nützlich und wird, wie Sie sagten, von Angular selbst verwendet.
Fasfsfgs
Ich versuche einem Beispiel von satchmorun zu folgen. Ich generiere zur Laufzeit etwas HTML, verwende aber nicht die Vorlage der Direktive. Ich verwende den Controller der Direktive, um eine Funktion anzugeben, die aus dem hinzugefügten HTML-Code aufgerufen werden soll, aber die Funktion wird nicht aufgerufen. Grundsätzlich habe ich diese Direktive: directives.directive ('abcXyz', Funktion ($ compile {return {einschränken: 'AE', erfordern: 'ngModel', Controller: Funktion ($ scope) {$ scope.function1 = function () {..};}, mein html ist: "<a href="" ng-click="function1('itemtype')">
Mark
Dies ist die einzige elegante Lösung, die Direktiven-API verfügbar machen kann, wenn Direktive kein Singleton ist! Ich benutze $scope.$parent[alias]es immer noch nicht gern, weil es für mich nach innerer eckiger API riecht. Aber ich kann immer noch keine elegantere Lösung für Nicht-Singleton-Direktiven finden. Andere Varianten wie das Senden von Ereignissen oder das Definieren eines leeren Objekts im übergeordneten Controller für Direktiven-API riechen noch mehr.
Ruslan Stelmachenko
3

Ich habe eine viel bessere Lösung.

Hier ist meine Direktive, ich habe die Objektreferenz in die Direktive eingefügt und diese durch Hinzufügen einer Aufruffunktion im Direktivencode erweitert.

app.directive('myDirective', function () {
    return {
        restrict: 'E',
        scope: {
        /*The object that passed from the cntroller*/
        objectToInject: '=',
        },
        templateUrl: 'templates/myTemplate.html',

        link: function ($scope, element, attrs) {
            /*This method will be called whet the 'objectToInject' value is changes*/
            $scope.$watch('objectToInject', function (value) {
                /*Checking if the given value is not undefined*/
                if(value){
                $scope.Obj = value;
                    /*Injecting the Method*/
                    $scope.Obj.invoke = function(){
                        //Do something
                    }
                }    
            });
        }
    };
});

Deklarieren der Direktive im HTML mit einem Parameter:

<my-directive object-to-inject="injectedObject"></ my-directive>

mein Controller:

app.controller("myController", ['$scope', function ($scope) {
   // object must be empty initialize,so it can be appended
    $scope.injectedObject = {};

    // now i can directly calling invoke function from here 
     $scope.injectedObject.invoke();
}];
Ashwini Jindal
quelle
Dies widerspricht grundsätzlich den Grundsätzen der Trennung von Bedenken. Sie stellen der Direktive ein in einem Controller instanziiertes Objekt zur Verfügung und delegieren die Verantwortung für die Verwaltung dieses Objekts (dh die Erstellung der Aufruffunktion) an die Direktive. Meiner Meinung nach NICHT die bessere Lösung.
Florin Vistig