Método de chamada no controlador de diretiva de outro controlador


118

Eu tenho uma diretiva que tem seu próprio controlador. Veja o código abaixo:

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

});

Este é um sistema de notificação de erros / notificações / avisos. O que eu quero fazer é de outro controlador (não um diretivo) para chamar a funçãoshow neste controlador. E, quando faço isso, também quero que minha função de link detecte que algumas propriedades foram alteradas e execute algumas animações.

Aqui está um código para exemplificar o que estou pedindo:

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

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

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

Portanto, ao chamar showno popdowncontrolador de diretiva, a função de link também deve ser disparada e executar uma animação. Como eu poderia conseguir isso?


Onde você está colocando a chamada para a popdowndiretiva na página - é apenas em um lugar onde todos os outros controladores devem ter acesso a ela, ou há vários popdowns em locais diferentes?
satchmorun de

meu index.html tem isto: <div ng-view> </div> <div popdown> </div> basicamente há apenas 1 instância popdown, pois deve estar disponível globalmente.
user253530

1
Acho que você pretendia escrever, popdown.show(...)não popdown.notify(...)é? Caso contrário, a função de notificação é um pouco confusa.
lanoxx

de onde vem isso popdown.notify? .notifiymétodo, quero dizer
Verde

Respostas:


167

Esta é uma pergunta interessante, e comecei a pensar em como implementaria algo assim.

Eu inventei isso (violino) ;

Basicamente, em vez de tentar chamar uma diretiva de um controlador, criei um módulo para abrigar toda a lógica popdown:

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

Coloquei duas coisas no módulo, uma factorypara a API, que pode ser injetada em qualquer lugar, e directiveoutra para definir o comportamento do elemento popdown real:

A fábrica apenas define um par de funções successe errore mantém o controle de um par de variáveis:

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

A diretiva obtém a API injetada em seu controlador e observa a api em busca de alterações (estou usando o bootstrap css por conveniência):

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

Então eu defino um appmódulo que depende 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); }
});

E o HTML se parece com:

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

Não tenho certeza se é completamente ideal, mas parecia uma maneira razoável de estabelecer a comunicação com uma diretiva popdown global.

Novamente, para referência, o violino .


10
+1 Nunca se deve chamar uma função em uma diretiva de fora da diretiva - é uma prática ruim. Usar um serviço para gerenciar o estado global que uma diretiva lê é supercomum e essa é a abordagem correta. Mais aplicativos incluem filas de notificação e diálogos modais.
Josh David Miller

7
Resposta realmente excepcional! Um exemplo tão útil para aqueles de nós que vêm de jQuery e Backbone
Brandon

11
Desta forma, é possível usar este módulo para instanciar várias diretivas na mesma visualização? Como posso chamar a função de sucesso ou erro de uma instância particular desta diretiva?
ira

3
@ira você provavelmente poderia mudar a fábrica para manter um mapa (ou lista) de status e objetos de mensagem e então usar um atributo de nome na diretiva para identificar qual item na lista você precisa. Portanto, em vez de chamar success(msg)no html, você chamaria sucess(name, msg)para selecionar a diretiva com o nome correto.
lanoxx

5
@JoshDavidMiller por que você considera uma prática ruim chamar um método em uma diretiva? Se uma diretiva encapsula alguma lógica DOM conforme pretendido, certamente é bastante natural expor uma API para que os controladores que a usam possam invocar seus métodos conforme necessário.
Paul Taylor

27

Você também pode usar eventos para acionar o Popdown.

Aqui está um violino baseado na solução de satchmorun. Ele dispensa o PopdownAPI e o controlador de nível superior, em vez $broadcastdos eventos de 'sucesso' e 'erro' na cadeia de escopo:

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

O módulo Popdown, então, registra funções de manipulador para esses eventos, por exemplo:

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

Isso funciona, pelo menos, e me parece uma solução bem dissociada. Vou deixar que outros interfiram se isso for considerado uma prática inadequada por algum motivo.


1
Uma desvantagem em que posso pensar é que na resposta selecionada você só precisa do PopdownAPI (facilmente disponível com DI). Neste, você precisa acessar o escopo do controlador para transmitir a mensagem. De qualquer forma, parece muito conciso.
Juliano de

Eu gosto mais disso do que da abordagem de serviço para casos de uso simples, pois mantém a complexidade baixa e ainda é fracamente acoplada
Patrick Favre

11

Você também pode expor o controlador da diretiva ao escopo pai, como o faz ngFormcom nameattribute: http://docs.angularjs.org/api/ng.directive:ngForm

Aqui você pode encontrar um exemplo muito básico de como isso poderia ser alcançado http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

Neste exemplo, tenho myDirectiveum controlador dedicado com $clearmétodo (uma espécie de API pública muito simples para a diretiva). Posso publicar este controlador no escopo pai e usar a chamada desse método fora da diretiva.


Isso requer um relacionamento entre os controladores, certo? Visto que OP queria um centro de mensagens, isso pode não ser o ideal para ele. Mas foi muito bom aprender sua abordagem também. É útil em muitas situações e, como você disse, o próprio angular o usa.
fasfsfgs

Estou tentando seguir um exemplo fornecido por satchmorun. Estou gerando algum html em tempo de execução, mas não estou usando o modelo de diretiva. Estou usando o controlador da diretiva para especificar uma função a ser chamada do html adicionado, mas a função não está sendo chamada. Basicamente, eu tenho esta diretiva: directives.directive ('abcXyz', function ($ compile {return {strict: 'AE', require: 'ngModel', controlador: function ($ scope) {$ scope.function1 = function () {..};}, meu html é: "<a href="" ng-click="function1('itemtype')">
Marcar em

Esta é a única solução elegante que pode expor a api diretiva se a diretiva não for um singleton! Ainda não gosto de usar $scope.$parent[alias]porque, para mim, cheira como usar API angular interna. Mas ainda não consigo encontrar uma solução mais elegante para diretivas não singleton. Outras variantes, como eventos de transmissão ou definir objeto vazio no controlador pai para API de diretiva cheiram ainda mais.
Ruslan Stelmachenko

3

Eu tenho uma solução muito melhor.

aqui está minha diretiva, injetei na referência de objeto na diretiva e estendi isso adicionando a função invoke no código da diretiva.

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

Declarando a diretiva no HTML com um parâmetro:

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

meu controlador:

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

Isso basicamente vai contra os princípios de separação de interesses. Você fornece à diretiva um objeto instanciado em um controlador e delega a responsabilidade de gerenciar esse objeto (ou seja, a criação da função invoke) à diretiva. Na minha opinião, NÃO é a melhor solução.
Florin Vistig
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.