Um controlador AngularJS pode chamar outro?

581

É possível ter um controlador usando outro?

Por exemplo:

Este documento HTML simplesmente imprime uma mensagem entregue pelo MessageCtrlcontrolador no messageCtrl.jsarquivo.

<html xmlns:ng="http://angularjs.org/">
<head>
    <meta charset="utf-8" />
    <title>Inter Controller Communication</title>
</head>
<body>
    <div ng:controller="MessageCtrl">
        <p>{{message}}</p>
    </div>

    <!-- Angular Scripts -->
    <script src="http://code.angularjs.org/angular-0.9.19.js" ng:autobind></script>
    <script src="js/messageCtrl.js" type="text/javascript"></script>
</body>
</html>

O arquivo do controlador contém o seguinte código:

function MessageCtrl()
{
    this.message = function() { 
        return "The current date is: " + new Date().toString(); 
    };
}

O que simplesmente imprime a data atual;

Se eu adicionasse outro controlador, DateCtrlque devolvesse a data em um formato específico MessageCtrl, como alguém faria isso? A estrutura de DI parece estar preocupada XmlHttpRequestse acessando serviços.

BanksySan
fonte
4
Este tópico do grupo do Google, groups.google.com/d/topic/angular/m_mn-8gnNt4/discussion , discute cinco maneiras pelas quais os controladores podem conversar entre si.
precisa saber é o seguinte
Já existem boas respostas aqui, então gostaria de salientar que, para o caso de uso específico mencionado, talvez um filtro AngularJS seja uma solução melhor? Só pensei que eu iria falar dele :)
Joe Dyndale

Respostas:

705

Existem várias maneiras de se comunicar entre controladores.

O melhor é provavelmente compartilhar um serviço:

function FirstController(someDataService) 
{
  // use the data service, bind to template...
  // or call methods on someDataService to send a request to server
}

function SecondController(someDataService) 
{
  // has a reference to the same instance of the service
  // so if the service updates state for example, this controller knows about it
}

Outra maneira é emitir um evento no escopo:

function FirstController($scope) 
{
  $scope.$on('someEvent', function(event, args) {});
  // another controller or even directive
}

function SecondController($scope) 
{
  $scope.$emit('someEvent', args);
}

Nos dois casos, você também pode se comunicar com qualquer diretiva.

Vojta
fonte
4
Olá, o primeiro exemplo exigiria que a página da Web estivesse ciente de todos os serviços na pilha. O que parece um cheiro ruim (?). Como no segundo, a página da web não precisaria fornecer o argumento $ scope?
BanksySan
54
O que? Por quê? Todos os controladores são injetados pelo DI da Angular.
Vojta
7
@ JoshNoe em 1 / você tem dois controladores (ou mais) e ambos recebem um serviço idêntico / compartilhado. Então, você tem várias maneiras de se comunicar, algumas delas mencionadas. Eu decidiria com base no seu caso de uso específico. Você pode colocar a lógica / estado compartilhado no serviço e os dois controladores delegam apenas para esse serviço ou até exportam o serviço para o modelo. Claro, o serviço pode também eventos de fogo ...
Vojta
137
Chegando tarde demais: vocês sabem que estão discutindo com o The Vojta, do Google, que trabalha no AngularJS, certo? :)
Suman 20/03
16
Não era óbvio para mim que, no meu HTML, o controlador emissor de eventos deve ser um nó filho do controlador de escuta para que ele funcione.
Djangonaut 18/05/2014
122

Veja este violino: http://jsfiddle.net/simpulton/XqDxG/

Assista também ao vídeo a seguir: Comunicação entre controladores

Html:

<div ng-controller="ControllerZero">
  <input ng-model="message" >
  <button ng-click="handleClick(message);">LOG</button>
</div>

<div ng-controller="ControllerOne">
  <input ng-model="message" >
</div>

<div ng-controller="ControllerTwo">
  <input ng-model="message" >
</div>

javascript:

var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
  var sharedService = {};

  sharedService.message = '';

  sharedService.prepForBroadcast = function(msg) {
    this.message = msg;
    this.broadcastItem();
  };

  sharedService.broadcastItem = function() {
    $rootScope.$broadcast('handleBroadcast');
  };

  return sharedService;
});

function ControllerZero($scope, sharedService) {
  $scope.handleClick = function(msg) {
    sharedService.prepForBroadcast(msg);
  };

  $scope.$on('handleBroadcast', function() {
    $scope.message = sharedService.message;
  });        
}

function ControllerOne($scope, sharedService) {
  $scope.$on('handleBroadcast', function() {
    $scope.message = 'ONE: ' + sharedService.message;
  });        
}

function ControllerTwo($scope, sharedService) {
  $scope.$on('handleBroadcast', function() {
    $scope.message = 'TWO: ' + sharedService.message;
  });
}

ControllerZero.$inject = ['$scope', 'mySharedService'];        

ControllerOne.$inject = ['$scope', 'mySharedService'];

ControllerTwo.$inject = ['$scope', 'mySharedService'];
adardesign
fonte
12
O violino e o vídeo acima compartilham um serviço. Aqui está um violino que usa $ âmbito $ Emit:. Jsfiddle.net/VxafF
Mark Rajcok
1
@adardesign: Eu adoraria ler o mesmo exemplo sucinto e significativo para directivas (obrigado por esta resposta também!)
sscarduzio
Ótima resposta, eu uso o myModule.service ('mySharedService', função ($ rootScope) {}) em vez do myModule.factory, mas funciona assim mesmo!
precisa saber é o seguinte
Excelente. Porém, eu tenho uma pergunta: Por que você adicionou um manipulador no ControllerZero? $ scope. $ on ('handleBroadcast', function () {$ scope.message = sharedService.message;});
ZooZ
O vídeo fornecido é realmente incrível! Parece que é isso que preciso consultar o estado de outro controlador de outro controlador. No entanto, isso não funciona usando a função "invocar". Funciona usando a ação "trigger". Com eficácia, se um controlador executar uma ação e tiver um novo estado, ele deverá transmiti-lo, e cabe a outros controladores ouvir essa transmissão e responder adequadamente. Ou melhor, execute a ação no serviço compartilhado e depois transmita o estado. Por favor, diga-me se meu entendimento está correto.
tarekahf 16/09
53

Se você deseja chamar um controlador para outro, existem quatro métodos disponíveis

  1. $ rootScope. $ emit () e $ rootScope. $ broadcast ()
  2. Se o Segundo controlador for filho, você poderá usar a comunicação filho pai.
  3. Use Serviços
  4. Tipo de hack - com a ajuda de angular.element ()

1. $ rootScope. $ Emit () e $ rootScope. $ Broadcast ()

O controlador e seu escopo podem ser destruídos, mas o $ rootScope permanece no aplicativo, é por isso que estamos usando o $ rootScope porque $ rootScope é o pai de todos os escopos.

Se você estiver realizando a comunicação de pai para filho e até o filho quiser se comunicar com seus irmãos, poderá usar $ broadcast

Se você estiver executando uma comunicação de filho para pai, sem irmãos envolvidos, você poderá usar $ rootScope. $ Emit

HTML

<body ng-app="myApp">
    <div ng-controller="ParentCtrl" class="ng-scope">
      // ParentCtrl
      <div ng-controller="Sibling1" class="ng-scope">
        // Sibling first controller
      </div>
      <div ng-controller="Sibling2" class="ng-scope">
        // Sibling Second controller
        <div ng-controller="Child" class="ng-scope">
          // Child controller
        </div>
      </div>
    </div>
</body>

Código Angularjs

 var app =  angular.module('myApp',[]);//We will use it throughout the example 
    app.controller('Child', function($rootScope) {
      $rootScope.$emit('childEmit', 'Child calling parent');
      $rootScope.$broadcast('siblingAndParent');
    });

app.controller('Sibling1', function($rootScope) {
  $rootScope.$on('childEmit', function(event, data) {
    console.log(data + ' Inside Sibling one');
  });
  $rootScope.$on('siblingAndParent', function(event, data) {
    console.log('broadcast from child in parent');
  });
});

app.controller('Sibling2', function($rootScope) {
  $rootScope.$on('childEmit', function(event, data) {
    console.log(data + ' Inside Sibling two');
  });
  $rootScope.$on('siblingAndParent', function(event, data) {
    console.log('broadcast from child in parent');
  });
});

app.controller('ParentCtrl', function($rootScope) {
  $rootScope.$on('childEmit', function(event, data) {
    console.log(data + ' Inside parent controller');
  });
  $rootScope.$on('siblingAndParent', function(event, data) {
    console.log('broadcast from child in parent');
  });
});

No console de código acima, $ emit 'childEmit' não chamará dentro de irmãos filhos e chamará somente dentro de pai, onde $ broadcast também é chamado dentro de irmãos e pai.Este é o lugar onde o desempenho entra em ação. preferível, se você estiver usando a comunicação filho-pai, porque ignora algumas verificações sujas.

2. Se o Segundo controlador for filho, você poderá usar a comunicação Pai-Filho.

É um dos melhores métodos. Se você deseja fazer a comunicação entre pais e filhos, onde os filhos desejam se comunicar com os pais imediatos , não seria necessário nenhum tipo de transmissão ou emissão de $, mas se você deseja fazer comunicação entre pais e filhos, precisará use service ou $ broadcast

Por exemplo HTML: -

<div ng-controller="ParentCtrl">
 <div ng-controller="ChildCtrl">
 </div>
</div>

Angularjs

 app.controller('ParentCtrl', function($scope) {
   $scope.value='Its parent';
      });
  app.controller('ChildCtrl', function($scope) {
   console.log($scope.value);
  });

Sempre que você estiver usando a comunicação filho-pai, o Angularjs procurará uma variável dentro do filho. Se não estiver presente no interior, escolherá ver os valores no controlador pai.

Serviços 3.Use

O AngularJS suporta os conceitos de "Separação de preocupações" usando a arquitetura de serviços. Os serviços são funções javascript e são responsáveis ​​por executar apenas tarefas específicas. Isso os torna uma entidade individual que pode ser mantida e testada . Serviços usados ​​para injetar usando o mecanismo de injeção de dependência do Angularjs.

Código Angularjs:

app.service('communicate',function(){
  this.communicateValue='Hello';
});

app.controller('ParentCtrl',function(communicate){//Dependency Injection
  console.log(communicate.communicateValue+" Parent World");
});

app.controller('ChildCtrl',function(communicate){//Dependency Injection
  console.log(communicate.communicateValue+" Child World");
});

Ele fornecerá a saída Hello Child World e Hello Parent World. De acordo com os documentos angulares dos serviços Singletons - Cada componente dependente de um serviço obtém uma referência à instância única gerada pela fábrica de serviços .

4.Kind of hack - com a ajuda de angular.element ()

Esse método obtém o scope () do elemento por seu método Id / exclusivo class.angular.element () retorna o elemento e o scope () fornece a variável $ scope de outra variável usando a variável $ scope de um controlador dentro de outro não é uma boa prática.

HTML: -

<div id='parent' ng-controller='ParentCtrl'>{{varParent}}
 <span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
 <div id='child' ng-controller='childCtrl'>{{varChild}}
   <span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>
 </div>
</div>

Angularjs: -

app.controller('ParentCtrl',function($scope){
 $scope.varParent="Hello Parent";
  $scope.getValueFromChild=function(){
  var childScope=angular.element('#child').scope();
  console.log(childScope.varChild);
  }
});

app.controller('ChildCtrl',function($scope){
 $scope.varChild="Hello Child";
  $scope.getValueFromParent=function(){
  var parentScope=angular.element('#parent').scope();
  console.log(parentScope.varParent);
  }
}); 

No código acima, os controladores estão mostrando seu próprio valor em HTML e, quando você clica no texto, obtém valores no console de acordo. Se você clicar no período dos controladores pai, o navegador consola o valor de child e vice-versa.

Shubham Nigam
fonte
52

Aqui está um exemplo de uma página de dois controladores que compartilham dados de serviço:

<!doctype html>
<html ng-app="project">
<head>
    <title>Angular: Service example</title>
    <script src="http://code.angularjs.org/angular-1.0.1.js"></script>
    <script>
var projectModule = angular.module('project',[]);

projectModule.factory('theService', function() {  
    return {
        thing : {
            x : 100
        }
    };
});

function FirstCtrl($scope, theService) {
    $scope.thing = theService.thing;
    $scope.name = "First Controller";
}

function SecondCtrl($scope, theService) {   
    $scope.someThing = theService.thing; 
    $scope.name = "Second Controller!";
}
    </script>
</head>
<body>  
    <div ng-controller="FirstCtrl">
        <h2>{{name}}</h2>
        <input ng-model="thing.x"/>         
    </div>

    <div ng-controller="SecondCtrl">
        <h2>{{name}}</h2>
        <input ng-model="someThing.x"/>             
    </div>
</body>
</html>

Também aqui: https://gist.github.com/3595424

exclsr
fonte
E se theServiceas atualizações thing.x, em seguida, que a mudança propageates automaticamente para o <input> S em FirstCtrle SecondCtrl, certo? E também é possível mudar thing.xdiretamente através de qualquer um dos dois <input> s (certo?).
KajMagnus
4
Sim. Todos os serviços Angular são singletons de aplicativos, o que significa que há apenas uma instância do serviço. Referência: docs.angularjs.org/guide/dev_guide.services.creating_services
exclsr
O link no meu comentário anterior é 404, então aqui está o guia de serviços, hoje, que os serviços de anotações são singletons: docs.angularjs.org/guide/services
exclsr
1
@exclsr Yes! Desculpe eu perdi isso antes
CodyBugstein
3
De longe, o melhor exemplo que já vi na web até agora. Obrigado
Sevenearths
33

Se você deseja emitir e transmitir eventos para compartilhar dados ou chamar funções entre controladores , consulte este link : e verifique a resposta em zbynour(responda com o máximo de votos). Estou citando sua resposta !!!

Se o escopo do firstCtrl for o pai do escopo do secondCtrl, seu código funcionará substituindo $ emit por $ broadcast no firstCtrl:

function firstCtrl($scope){
    $scope.$broadcast('someEvent', [1,2,3]);
}

function secondCtrl($scope){
    $scope.$on('someEvent', function(event, mass) {console.log(mass)});
}

Caso não exista relação pai-filho entre seus escopos, você pode injetar $ rootScope no controlador e transmitir o evento para todos os escopos filhos (por exemplo, também secondCtrl).

function firstCtrl($rootScope){
    $rootScope.$broadcast('someEvent', [1,2,3]);
}

Finalmente, quando você precisar despachar o evento do controlador filho para escopos para cima, poderá usar $ scope. $ Emit. Se o escopo de firstCtrl for o pai do escopo secondCtrl:

function firstCtrl($scope){
    $scope.$on('someEvent', function(event, data) { console.log(data); });
}

function secondCtrl($scope){
    $scope.$emit('someEvent', [1,2,3]);
}
SharpCoder
fonte
24

Mais dois problemas: (Abordagem sem serviço)

1) Para controlador $scopepai- filho - Uso do controlador pai para emitir / transmitir eventos. http://jsfiddle.net/laan_sachin/jnj6y/

2) Utilização $rootScopeem controladores não relacionados. http://jsfiddle.net/VxafF/

Cavaleiro das Trevas
fonte
Qual o motivo de toda essa complexidade com os eventos? Por que não fazer algo assim? jsfiddle.net/jnj6y/32
DFR
Depende de que tipo de relacionamento entre pais e filhos está correto. Pode ser uma hierarquia DOM, caso os eventos permitam dissociar as coisas.
precisa saber é o seguinte
17

Na verdade, o uso de emissão e transmissão é ineficiente porque o evento borbulha para cima e para baixo na hierarquia do escopo, o que pode facilmente se degradar em agrupamento de desempenho para um aplicativo complexo.

Eu sugeriria usar um serviço. Aqui está como eu o implementei recentemente em um dos meus projetos - https://gist.github.com/3384419 .

Ideia básica - registre um barramento de sub-evento / pub como um serviço. Em seguida, injete esse barramento de eventos sempre que precisar para assinar ou publicar eventos / tópicos.

numan salati
fonte
5

Eu também sei desse jeito.

angular.element($('#__userProfile')).scope().close();

Mas não uso muito, porque não gosto de usar seletores jQuery no código angular.

Andrey Korchak
fonte
a melhor resposta. Tão simples e fácil ... =)
zVictor
3
@zVictor, esse é realmente um tipo de abordagem de "último recurso". Funciona, mas está fora do escopo para forçar o caminho de volta. Isso está usando a manipulação do DOM para forçar algo a ser feito, em vez de fazê-lo programaticamente. É simples, funciona, mas não é escalável.
Brian Noah
2
@BrianNoah, é verdade. Tudo bem, use esse código para protótipos ou alguns experimentos, mas não para o código de produção.
Andrey Korchak
1
Isso é o pior que pode ser feito. Manipulação de DOM em serviços e acesso direto ao escopo.
Mattia Franchetto
3

Existe um método não dependente de serviços $broadcastou $emit. Não é adequado em todos os casos, mas se você tiver 2 controladores relacionados que podem ser abstraídos em diretivas, poderá usar a requireopção na definição de diretiva. É mais provável como o ngModel e o ngForm se comunicam. Você pode usar isso para se comunicar entre controladores de diretiva que estão aninhados ou no mesmo elemento.

Para uma situação de pai / filho, o uso seria o seguinte:

<div parent-directive>
  <div inner-directive></div>
</div>

E os principais pontos para fazê-lo funcionar: Na diretiva pai, com os métodos a serem chamados, você deve defini-los em this(não em $scope):

controller: function($scope) {
  this.publicMethodOnParentDirective = function() {
    // Do something
  }
}

Na definição de diretiva filho, você pode usar a requireopção para que o controlador pai seja passado para a função de link (para que você possa chamar funções nele a partir scopeda diretiva filho.

require: '^parentDirective',
template: '<span ng-click="onClick()">Click on this to call parent directive</span>',
link: function link(scope, iElement, iAttrs, parentController) {
  scope.onClick = function() {
    parentController.publicMethodOnParentDirective();
  }
}

O acima pode ser visto em http://plnkr.co/edit/poeq460VmQER8Gl9w8Oz?p=preview

Uma diretiva irmão é usada da mesma forma, mas ambas as diretivas no mesmo elemento:

<div directive1 directive2>
</div>

Usado criando um método em directive1:

controller: function($scope) {
  this.publicMethod = function() {
    // Do something
  }
}

E na diretiva2, isso pode ser chamado usando a requireopção que resulta na passagem do siblingController para a função de link:

require: 'directive1',
template: '<span ng-click="onClick()">Click on this to call sibling directive1</span>',
link: function link(scope, iElement, iAttrs, siblingController) {
  scope.onClick = function() {
    siblingController.publicMethod();
  }
}

Isso pode ser visto em http://plnkr.co/edit/MUD2snf9zvadfnDXq85w?p=preview .

Os usos disso?

  • Pai: qualquer caso em que os elementos filhos precisem "se registrar" com um pai. Muito parecido com o relacionamento entre ngModel e ngForm. Isso pode adicionar um certo comportamento que pode afetar os modelos. Você também pode ter algo puramente baseado em DOM, em que um elemento pai precisa gerenciar as posições de determinados filhos, por exemplo, gerenciar ou reagir à rolagem.

  • Irmão: permite que uma diretiva tenha seu comportamento modificado. O ngModel é o caso clássico, para adicionar analisadores / validação ao uso do ngModel nas entradas.

Michal Charemza
fonte
3

Não sei se isso está fora dos padrões, mas se você tiver todos os seus controladores no mesmo arquivo, poderá fazer algo assim:

app = angular.module('dashboardBuzzAdmin', ['ngResource', 'ui.bootstrap']);

var indicatorsCtrl;
var perdiosCtrl;
var finesCtrl;

app.controller('IndicatorsCtrl', ['$scope', '$http', function ($scope, $http) {
  indicatorsCtrl = this;
  this.updateCharts = function () {
    finesCtrl.updateChart();
    periodsCtrl.updateChart();
  };
}]);

app.controller('periodsCtrl', ['$scope', '$http', function ($scope, $http) {
  periodsCtrl = this;
  this.updateChart = function() {...}
}]);

app.controller('FinesCtrl', ['$scope', '$http', function ($scope, $http) {
  finesCtrl = this;
  this.updateChart = function() {...}
}]);

Como você pode ver indicadores, o Ctrl está chamando as funções updateChart dos outros dois controladores ao chamar updateCharts.

tomascharad
fonte
2

Você pode injetar o serviço '$ controller' no controlador pai (MessageCtrl) e instanciar / injetar o controlador filho (DateCtrl) usando:
$scope.childController = $controller('childController', { $scope: $scope.$new() });

Agora você pode acessar os dados do seu controlador filho chamando seus métodos, pois é um serviço.
Deixe-me saber se houver algum problema.

Smrutiranjan Sahu
fonte
1

A seguir, é apresentada uma publish-subscribeabordagem independente do JS angular.

Controlador de parâmetros de pesquisa

//Note: Multiple entities publish the same event
regionButtonClicked: function () 
{
        EM.fireEvent('onSearchParamSelectedEvent', 'region');
},

plantButtonClicked: function () 
{
        EM.fireEvent('onSearchParamSelectedEvent', 'plant');
},

Controlador de opções de pesquisa

//Note: It subscribes for the 'onSearchParamSelectedEvent' published by the Search Param Controller
localSubscribe: function () {
        EM.on('onSearchParamSelectedEvent', this.loadChoicesView, this);

});


loadChoicesView: function (e) {

        //Get the entity name from eData attribute which was set in the event manager
        var entity = $(e.target).attr('eData');

        console.log(entity);

        currentSelectedEntity = entity;
        if (entity == 'region') {
            $('.getvalue').hide();
            this.loadRegionsView();
            this.collapseEntities();
        }
        else if (entity == 'plant') {
            $('.getvalue').hide();
            this.loadPlantsView();
            this.collapseEntities();
        }


});

Gerente de eventos

myBase.EventManager = {

    eventArray:new Array(),


    on: function(event, handler, exchangeId) {
        var idArray;
        if (this.eventArray[event] == null) {
            idArray = new Array();
        } else { 
            idArray = this.eventArray[event];
        }
        idArray.push(exchangeId);
        this.eventArray[event] = idArray;

        //Binding using jQuery
        $(exchangeId).bind(event, handler);
    },

    un: function(event, handler, exchangeId) {

        if (this.eventArray[event] != null) {
            var idArray = this.eventArray[event];
            idArray.pop(exchangeId);
            this.eventArray[event] = idArray;

            $(exchangeId).unbind(event, handler);
        }
    },

    fireEvent: function(event, info) {
        var ids = this.eventArray[event];

        for (idindex = 0; idindex < ids.length; idindex++) {
            if (ids[idindex]) {

                //Add attribute eData
                $(ids[idindex]).attr('eData', info);
                $(ids[idindex]).trigger(event);
            }
        }
    }
};

Global

var EM = myBase.EventManager;
LCJ
fonte
1

No angular 1.5, isso pode ser feito da seguinte maneira:

(function() {
  'use strict';

  angular
    .module('app')
    .component('parentComponent',{
      bindings: {},
      templateUrl: '/templates/products/product.html',
      controller: 'ProductCtrl as vm'
    });

  angular
    .module('app')
    .controller('ProductCtrl', ProductCtrl);

  function ProductCtrl() {
    var vm = this;
    vm.openAccordion = false;

    // Capture stuff from each of the product forms
    vm.productForms = [{}];

    vm.addNewForm = function() {
      vm.productForms.push({});
    }
  }

}());

Este é o componente pai. Nisto, criei uma função que empurra outro objeto para a minha productFormsmatriz - note - este é apenas o meu exemplo, essa função pode ser algo realmente.

Agora podemos criar outro componente que fará uso de require:

(function() {
  'use strict';

  angular
    .module('app')
    .component('childComponent', {
      bindings: {},
      require: {
        parent: '^parentComponent'
      },
      templateUrl: '/templates/products/product-form.html',
      controller: 'ProductFormCtrl as vm'
    });

  angular
    .module('app')
    .controller('ProductFormCtrl', ProductFormCtrl);

  function ProductFormCtrl() {
    var vm = this;

    // Initialization - make use of the parent controllers function
    vm.$onInit = function() {
      vm.addNewForm = vm.parent.addNewForm;
    };  
  }

}());

Aqui, o componente filho está criando uma referência à função do componente pai, addNewFormque pode ser vinculada ao HTML e chamada como qualquer outra função.

Katana24
fonte