Chiama il metodo nel controller di direttiva da un altro controller


118

Ho una direttiva che ha il proprio controller. Vedere il codice seguente:

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

});

Questo è pensato per essere un sistema di notifica per errori / notifiche / avvisi. Quello che voglio fare è da un altro controller (non una direttiva) per chiamare la funzioneshow su questo controller. E quando lo faccio, vorrei anche che la mia funzione di collegamento rilevi che alcune proprietà sono cambiate ed esegua alcune animazioni.

Ecco del codice per esemplificare ciò che sto chiedendo:

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

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

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

Quindi, quando si chiama showil popdowncontroller della direttiva, anche la funzione di collegamento dovrebbe essere attivata ed eseguire un'animazione. Come potrei ottenerlo?


Dove collochi la chiamata alla popdowndirettiva sulla pagina: è solo in un punto in cui si suppone che gli altri controller abbiano tutti accesso ad esso o ci sono diversi popup in luoghi diversi?
satchmorun

il mio index.html ha questo: <div ng-view> </div> <div popdown> </div> fondamentalmente c'è solo 1 istanza di popdown poiché dovrebbe essere disponibile a livello globale.
user253530

1
Penso che volevi scrivere popdown.show(...)invece di, popdown.notify(...)è vero? Altrimenti la funzione di notifica è un po 'confusa.
lanoxx

da dove viene popdown.notify? .notifiymetodo, voglio dire
Verde

Risposte:


167

Questa è una domanda interessante e ho iniziato a pensare a come implementare qualcosa di simile.

Mi è venuto in mente questo (violino) ;

Fondamentalmente, invece di provare a chiamare una direttiva da un controller, ho creato un modulo per ospitare tutta la logica del popdown:

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

Ho inserito due cose nel modulo, una factoryper l'API che può essere iniettata ovunque e ladirective per definire il comportamento dell'elemento popdown effettivo:

La fabbrica appena definisce un paio di funzioni successe di errore tiene traccia di una coppia di variabili:

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

La direttiva ottiene l'API iniettata nel suo controller e controlla l'API per le modifiche (sto usando bootstrap css per comodità):

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

Quindi definisco un appmodulo che dipende da 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); }
});

E l'HTML ha questo aspetto:

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

Non sono sicuro che sia completamente ideale, ma sembrava un modo ragionevole per impostare la comunicazione con una direttiva di popdown globale.

Ancora una volta, per riferimento, il violino .


10
+1 Non si dovrebbe mai chiamare una funzione in una direttiva dall'esterno della direttiva: è una cattiva pratica. Utilizzare un servizio per gestire lo stato globale letto da una direttiva è molto comune e questo è l'approccio corretto. Altre applicazioni includono code di notifica e finestre di dialogo modali.
Josh David Miller,

7
Risposta davvero eccezionale! Un esempio così utile per quelli di noi provenienti da jQuery e Backbone
Brandon

11
In questo modo è possibile utilizzare questo modulo per istanziare più direttive nella stessa vista? Come posso chiamare la funzione di successo o errore di una particolare istanza di questa direttiva?
ira

3
@ira potresti probabilmente cambiare la factory per mantenere una mappa (o un elenco) di oggetti di stato e messaggio e quindi utilizzare un attributo name sulla direttiva per identificare quale elemento nell'elenco ti serve. Quindi invece di chiamare success(msg)l'html dovresti chiamare sucess(name, msg)per selezionare la direttiva con il nome corretto.
lanoxx

5
@JoshDavidMiller perché ritieni che sia una cattiva pratica chiamare un metodo su una direttiva? Se una direttiva incapsula la logica del DOM come previsto, sicuramente è abbastanza naturale esporre un'API in modo che i controller che la utilizzano possano invocare i suoi metodi secondo necessità?
Paul Taylor

27

È inoltre possibile utilizzare gli eventi per attivare il popup.

Ecco un violino basato sulla soluzione di satchmorun. Elimina l'API Popdown e il controller di livello superiore invece $broadcasts "successo" e gli eventi "errore" lungo la catena dell'ambito:

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

Il modulo Popdown quindi registra le funzioni del gestore per questi eventi, ad esempio:

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

Questo funziona, almeno, e mi sembra una soluzione ben disaccoppiata. Lascerò che altri intervengano se questa è considerata una cattiva pratica per qualche motivo.


1
Uno svantaggio a cui posso pensare è che nella risposta selezionata hai solo bisogno dell'API Popdown (facilmente disponibile con DI). In questo è necessario accedere all'ambito del controller per trasmettere il messaggio. Ad ogni modo, sembra molto conciso.
Julian

Mi piace di più rispetto all'approccio al servizio per casi d'uso semplici in quanto mantiene bassa la complessità ed è ancora vagamente accoppiato
Patrick Favre

11

Potresti anche esporre il controller della direttiva all'ambito genitore, come ngFormcon l' nameattributo: http://docs.angularjs.org/api/ng.directive:ngForm

Qui puoi trovare un esempio molto semplice di come potrebbe essere raggiunto http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

In questo esempio ho un myDirectivecontroller dedicato con $clearmetodo (una sorta di API pubblica molto semplice per la direttiva). Posso pubblicare questo controller nell'ambito genitore e usare chiamare questo metodo al di fuori della direttiva.


Ciò richiede una relazione tra i controllori, giusto? Poiché OP voleva un centro messaggi, questo potrebbe non essere l'ideale per lui. Ma è stato anche molto bello imparare il tuo approccio. È utile in molte situazioni e, come hai detto tu, lo stesso angular lo usa.
fasfsfgs

Sto cercando di seguire un esempio fornito da satchmorun. Sto generando un po 'di html in fase di esecuzione, ma non sto usando il modello di direttiva. Sto usando il controller della direttiva per specificare una funzione da chiamare dall'html aggiunto ma la funzione non viene chiamata. Fondamentalmente, ho questa direttiva: directives.directive ('abcXyz', function ($ compile {return {limit: 'AE', require: 'ngModel', controller: function ($ scope) {$ scope.function1 = function () {..};}, il mio html è: "<a href="" ng-click="function1('itemtype')">
Marco

Questa è l'unica soluzione elegante che può esporre la direttiva api se la direttiva non è un singleton! Ancora non mi piace usare $scope.$parent[alias]perché per me odora come usare l'API angolare interna. Ma ancora non riesco a trovare una soluzione più elegante per le direttive non singleton. Altre varianti come la trasmissione di eventi o la definizione di oggetti vuoti nel controller principale per l'API direttiva hanno un odore ancora maggiore.
Ruslan Stelmachenko

3

Ho una soluzione molto migliore.

ecco la mia direttiva, ho inserito il riferimento all'oggetto nella direttiva e l'ho esteso aggiungendo la funzione invoke nel codice della direttiva.

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

Dichiarare la direttiva nell'HTML con un parametro:

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

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

Questo fondamentalmente va contro la separazione dei principi aziendali. Fornisci alla direttiva un oggetto istanziato in un controller e deleghi la responsabilità della gestione di quell'oggetto (cioè la creazione della funzione invoke) alla direttiva. A mio parere, NON la soluzione migliore.
Florin Vistig
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.