web-dev-qa-db-fra.com

Méthode d'appel dans le contrôleur de directive à partir d'un autre contrôleur

J'ai une directive qui a son propre contrôleur. Voir le code ci-dessous:

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

});

Il s’agit d’un système de notification des erreurs/notifications/avertissements. Ce que je veux faire est d’appeler la fonction show de ce contrôleur à partir d’un autre contrôleur (et non pas directif). Et quand je le ferais, je voudrais aussi que ma fonction de lien détecte le changement de certaines propriétés et exécute certaines animations.

Voici un code pour illustrer ce que je demande:

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

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

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

Ainsi, lors de l'appel de show sur le contrôleur de directive popdown, la fonction de liaison doit également être déclenchée et exécuter une animation. Comment pourrais-je atteindre cela?

118
user253530

C'est une question intéressante, et j'ai commencé à réfléchir à la façon dont je mettrais en œuvre quelque chose comme ça.

Je suis venu avec this (violon) ;

En gros, au lieu d’essayer d’appeler une directive à partir d’un contrôleur, j’ai créé un module pour héberger toute la logique du popdown:

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

J'ai mis deux choses dans le module, un factory pour l'API qui peut être injecté n'importe où et le directive pour définir le comportement de l'élément de menu déroulant réel:

La fabrique ne définit que quelques fonctions success et error et garde trace de quelques variables:

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 directive obtient l'API injectée dans son contrôleur et surveille les modifications de l'API (j'utilise bootstrap css pour plus de commodité):

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

Ensuite, je définis un module app qui dépend de 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); }
});

Et le HTML ressemble à:

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

Je ne suis pas sûr que ce soit tout à fait idéal, mais cela semblait être un moyen raisonnable de mettre en place une communication avec une directive globale.

Encore une fois, pour référence, le violon .

167
satchmorun

Vous pouvez également utiliser des événements pour déclencher le Popdown.

Voici un violon basé sur la solution de satchmorun. Il supprime PopdownAPI et le contrôleur de niveau supérieur à la place des événements $broadcasts 'success' et 'error' dans la chaîne d'étendue:

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

Le module Popdown enregistre ensuite les fonctions de gestionnaire pour ces événements, par exemple:

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

Cela fonctionne au moins et me semble être une solution bien découplée. Je laisserai les autres intervenir si cela est considéré comme une mauvaise pratique pour une raison quelconque.

26
Aron

Vous pouvez également exposer le contrôleur de la directive à la portée parente, comme ngForm avec l'attribut name: http://docs.angularjs.org/api/ng.directive:ngForm =

Ici vous pouvez trouver un exemple très basique comment cela pourrait être réalisé http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

Dans cet exemple, j'ai myDirective avec un contrôleur dédié avec la méthode $clear (sorte d'API publique très simple pour la directive). Je peux publier ce contrôleur dans l'étendue parent et utiliser l'appel de cette méthode en dehors de la directive.

11
luacassus

J'ai une bien meilleure solution.

voici ma directive, j’ai injecté une référence d’objet dans la directive et j’ai étendu cela en ajoutant une fonction invoke dans le code de la directive.

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

Déclarer la directive dans le HTML avec un paramètre:

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

mon contrôleur:

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();
}];
3
Ashwini Jindal