AngularJS: Como posso passar variáveis ​​entre controladores?

326

Eu tenho dois controladores angulares:

function Ctrl1($scope) {
    $scope.prop1 = "First";
}

function Ctrl2($scope) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

Não posso usar por Ctrl1dentro Ctrl2porque é indefinido. No entanto, se eu tentar passar assim ...

function Ctrl2($scope, Ctrl1) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

Eu recebo um erro. Alguém sabe como fazer isso?

Fazendo

Ctrl2.prototype = new Ctrl1();

Também falha.

NOTA: Esses controladores não estão aninhados um no outro.

dopatraman
fonte
Existem muitas maneiras, mas a melhor maneira é o relógio angular. Sempre que usamos uma estrutura, é a melhor maneira de usar seus próprios métodos para o trabalho, não se esqueça disso
pejman
Achei este blog muito útil Blog
Black Mamba

Respostas:

503

Uma maneira de compartilhar variáveis ​​entre vários controladores é criar um serviço e injetá-lo em qualquer controlador em que você deseja usá-lo.

Exemplo simples de serviço:

angular.module('myApp', [])
    .service('sharedProperties', function () {
        var property = 'First';

        return {
            getProperty: function () {
                return property;
            },
            setProperty: function(value) {
                property = value;
            }
        };
    });

Usando o serviço em um controlador:

function Ctrl2($scope, sharedProperties) {
    $scope.prop2 = "Second";
    $scope.both = sharedProperties.getProperty() + $scope.prop2;
}

Isso é descrito muito bem neste blog (lição 2 e em particular).

Descobri que se você deseja vincular a essas propriedades em vários controladores, funcionará melhor se você vincular à propriedade de um objeto em vez de um tipo primitivo (booleano, string, número) para manter a referência vinculada.

Exemplo: em var property = { Property1: 'First' };vez de var property = 'First';.


ATUALIZAÇÃO: Para (espero) tornar as coisas mais claras, aqui está um violino que mostra um exemplo de:

  • Vinculação a cópias estáticas do valor compartilhado (em myController1)
    • Ligação a uma primitiva (sequência)
    • Vinculação à propriedade de um objeto (salva em uma variável de escopo)
  • Vinculação a valores compartilhados que atualizam a interface do usuário à medida que os valores são atualizados (em myController2)
    • Vinculação a uma função que retorna uma primitiva (string)
    • Vinculação à propriedade do objeto
    • Associação bidirecional à propriedade de um objeto
Gloopy
fonte
5
Nesse caso - como o escopo do Ctrl2 "saberia" quando sharedProperties.getProperty () altera o valor?
OpherV
5
Se você deseja que sua interface do usuário seja atualizada sempre que a propriedade for alterada, você poderá mudar bothpara ser uma função e ela será chamada / reavaliada durante o processo de resumo angular. Veja este violino para um exemplo. Além disso, se você vincular à propriedade de um objeto, poderá usá-lo diretamente em sua visualização e ele será atualizado conforme os dados forem alterados de maneira semelhante a este exemplo .
amigos estão dizendo
11
Se você deseja detectar e reagir a alterações em seu controlador, uma opção é adicionar a getProperty()função ao escopo e usar $ scope. $ Watch como neste exemplo . Espero que esses exemplos ajudem!
amigos estão dizendo
1
Há um problema aqui, pois os serviços devem ser sem estado. Armazenar uma propriedade dentro de um serviço está errado (mas é conveniente). Comecei a usar $ cacheFactory para ler e gravar dados. Eu uso quase um serviço idêntico ao Gloopy, mas em vez de armazenar o estado no serviço, ele está agora no cache. Primeiro, crie um serviço de cache: angular.module ('CacheService', ['ng']) .factory ('CacheService', função ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Inclua em seu app.js, injete-o no serviço, use-o assim: retorne CacheService.get (key); ou CacheService.put (chave, valor);
Jordan Papaleo
4
Tentando entender como e por que essa resposta usa, e .servicenão .factorycomo descrito nos documentos angulares. Por que essa resposta é votada tão alto quando a documentação usa um método diferente?
Pspahn 6/04
44

Eu gosto de ilustrar coisas simples com exemplos simples :)

Aqui está um Serviceexemplo muito simples :


angular.module('toDo',[])

.service('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  this.dataObj = _dataObj;
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

E aqui o jsbin

E aqui está um Factoryexemplo muito simples :


angular.module('toDo',[])

.factory('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  return {
    dataObj: _dataObj
  };
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

E aqui o jsbin


Se isso é muito simples, aqui está um exemplo mais sofisticado

Consulte também a resposta aqui para comentários relacionados a práticas recomendadas

Dmitri Zaitsev
fonte
1
sim, eu concordo com você. Sempre tente simplificar as coisas.
Evan Hu
Qual é o sentido de declarar var _dataObj = {};quando você retorna uma referência direta a ele ..? Isso não é particular . No primeiro exemplo você pode fazer this.dataObj = {};e no segundo return { dataObj: {} };é uma declaração de variável inútil IMHO.
TJ
@TJ O objetivo é compartilhar essa variável entre outros componentes. É um exemplo básico que ilustra o conceito de compartilhamento. A variável é privada dentro do bloco e você a expõe como variável pública usando o padrão revelador. Dessa forma, há uma separação de responsabilidades entre manter a variável e usá-la.
Dmitri Zaitsev
@DmitriZaitsev você diz "exemplos simples", mas a menos que você mostre adequadamente como usar o estado privado, você está apenas confundindo as pessoas. Não há estado privado no seu exemplo, desde que você retorne uma referência direta.
TJ
@TJ Não vejo nada de confuso. Uma variável privada pode ser exposta por um módulo. Sinta-se livre para escrever uma resposta melhor.
Dmitri Zaitsev
26

--- Eu sei que esta resposta não é para esta pergunta, mas quero que as pessoas que leem essa pergunta e queiram lidar com serviços como fábricas evitem problemas ao fazer isso ----

Para isso, você precisará usar um Serviço ou uma Fábrica.

Os serviços são a MELHOR PRÁTICA para compartilhar dados entre controladores não aninhados.

Uma anotação muito boa sobre esse tópico sobre compartilhamento de dados é como declarar objetos. Tive azar porque caí em uma armadilha do AngularJS antes de ler sobre isso e fiquei muito frustrado. Então, deixe-me ajudá-lo a evitar esse problema.

Eu li no "ng-book: O livro completo sobre o AngularJS" que os modelos ng do AngularJS criados nos controladores como dados nus estão ERRADOS!

Um elemento $ scope deve ser criado assim:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // best practice, always use a model
  $scope.someModel = {
    someValue: 'hello computer'
  });

E não é assim:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // anti-pattern, bare value
  $scope.someBareValue = 'hello computer';
  };
});

Isso ocorre porque é recomendado (BEST PRACTICE) para o DOM (documento html) conter as chamadas como

<div ng-model="someModel.someValue"></div>  //NOTICE THE DOT.

Isso é muito útil para controladores aninhados se você desejar que seu controlador filho possa alterar um objeto do controlador pai ....

Porém, no seu caso, você não deseja escopos aninhados, mas há um aspecto semelhante para obter objetos dos serviços para os controladores.

Digamos que você tenha o seu serviço 'Factory' e, no espaço de retorno, existe um objetoA que contém o objetoB que contém o objetoC.

Se do seu controlador você deseja colocar o objetoC no seu escopo, é um erro dizer:

$scope.neededObjectInController = Factory.objectA.objectB.objectC;

Isso não vai funcionar ... Em vez disso, use apenas um ponto.

$scope.neededObjectInController = Factory.ObjectA;

Em seguida, no DOM, você pode chamar objectC a partir de objectA. Essa é uma prática recomendada relacionada às fábricas e, o mais importante, ajudará a evitar erros inesperados e não alcançáveis.

AFP_555
fonte
2
Eu acho que essa é uma boa resposta, mas é bem difícil de digerir.
Psp5 #
17

Solução sem criar Serviço, usando $ rootScope:

Para compartilhar propriedades entre os Controladores de aplicativos, você pode usar Angular $ rootScope. Essa é outra opção para compartilhar dados, colocando-os para que as pessoas saibam sobre eles.

A maneira preferida de compartilhar alguma funcionalidade entre os Controladores é Serviços, para ler ou alterar uma propriedade global, você pode usar $ rootscope.

var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = true;
}]);

app.controller('Ctrl2', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = false;
}]);

Usando $ rootScope em um modelo (acesse as propriedades com $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
fonte
5
Você está usando variáveis ​​com escopo global nesse ponto que se desvia da idéia do AngularJS de definir o escopo local de tudo dentro de suas várias estruturas. A adição de um arquivo de variável global alcançaria a mesma coisa e facilitaria a localização de onde a variável é definida originalmente. De qualquer maneira, não sugerido.
Organiccat 18/09/14
4
@Organiccat - Entendo sua preocupação e é por isso que eu já mencionei que a maneira preferida serão os serviços, sem dúvida. Mas você angular fornece esse caminho também. É com você que você deseja gerenciar o seu global. Eu tive um cenário em que essa abordagem funcionou melhor para mim.
Sanjeev
8

A amostra acima funcionou como um encanto. Fiz uma modificação apenas no caso de precisar gerenciar vários valores. Eu espero que isso ajude!

app.service('sharedProperties', function () {

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});
Juan Zamora
fonte
1
Também criei uma amostra usando um serviço para compartilhar dados entre diferentes controladores. Espero que vocês gostem. jsfiddle.net/juazammo/du53553a/1
Juan Zamora
1
Mesmo que funcione, essa é geralmente a sintaxe para .factory. Um .servicedeve ser utilizado "se você definir o seu serviço como um tipo / classe" como por docs.angularjs.org/api/auto/service/$provide#service
Dmitri Zaitsev
1
Dmitri, você está certo, no entanto, os caras angular da minha perspectiva, apenas mudou um pouco o conceito que eu tinha entre serviços (fachadas) e fábricas .... oh bem ....
Juan Zamora
1
E me corrija se estiver errado, os serviços pretendem retornar algo que pode ser um objeto ou um valor. As fábricas destinam-se a criar objetos. Uma fachada que, na verdade, é uma coleção de funcionalidades que retornam algo, é o que eu pensava em serviços onde. Incluindo a invocação de funcionalidades das fábricas. Mais uma vez, estou entrando na noção básica do que é isso para mim e não do que realmente é da perspectiva angular. (Abstract Factory dofactory.com/net/abstract-factory-design-pattern ) e uma abordagem Adapter é o que vou expor como um serviço
Juan Zamora
1
Verifique o padrão do adaptador aqui dofactory.com/net/adapter-design-pattern
Juan Zamora
6

Eu costumo usar valores, feliz por qualquer um discutir por que essa é uma má idéia.

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

myApp.value('sharedProperties', {}); //set to empty object - 

Injete o valor de acordo com um serviço.

Definido em ctrl1:

myApp.controller('ctrl1', function DemoController(sharedProperties) {
  sharedProperties.carModel = "Galaxy";
  sharedProperties.carMake = "Ford";
});

e acesso a partir de ctrl2:

myApp.controller('ctrl2', function DemoController(sharedProperties) {
  this.car = sharedProperties.carModel + sharedProperties.carMake; 

});
Chama refrigerada
fonte
como isso é diferente de usar um serviço?
Dopatraman
5

O exemplo a seguir mostra como passar variáveis ​​entre controladores irmãos e executar uma ação quando o valor for alterado.

Exemplo de caso de uso: você tem um filtro em uma barra lateral que altera o conteúdo de outra visualização.

angular.module('myApp', [])

  .factory('MyService', function() {

    // private
    var value = 0;

    // public
    return {
      
      getValue: function() {
        return value;
      },
      
      setValue: function(val) {
        value = val;
      }
      
    };
  })
  
  .controller('Ctrl1', function($scope, $rootScope, MyService) {

    $scope.update = function() {
      MyService.setValue($scope.value);
      $rootScope.$broadcast('increment-value-event');
    };
  })
  
  .controller('Ctrl2', function($scope, MyService) {

    $scope.value = MyService.getValue();

    $scope.$on('increment-value-event', function() {    
      $scope.value = MyService.getValue();
    });
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="myApp">
  
  <h3>Controller 1 Scope</h3>
  <div ng-controller="Ctrl1">
    <input type="text" ng-model="value"/>
    <button ng-click="update()">Update</button>
  </div>
  
  <hr>
  
  <h3>Controller 2 Scope</h3>
  <div ng-controller="Ctrl2">
    Value: {{ value }}
  </div>  

</div>

Zanon
fonte
4

Eu gostaria de contribuir para essa pergunta, salientando que a maneira recomendada de compartilhar dados entre controladores e até diretivas é usando serviços (fábricas), como já foi apontado, mas também gostaria de fornecer um exemplo prático de como fazer isso.

Aqui está o plunker de trabalho: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Primeiro, crie seu serviço , que terá seus dados compartilhados :

app.factory('SharedService', function() {
  return {
    sharedObject: {
      value: '',
      value2: ''
    }
  };
});

Em seguida, basta injetar nos controladores e pegar os dados compartilhados no seu escopo:

app.controller('FirstCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('SecondCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('MainCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

Você também pode fazer isso pelas suas diretivas , da mesma maneira:

app.directive('myDirective',['SharedService', function(SharedService){
  return{
    restrict: 'E',
    link: function(scope){
      scope.model = SharedService.sharedObject;
    },
    template: '<div><input type="text" ng-model="model.value"/></div>'
  }
}]);

Espero que esta resposta prática e limpa possa ser útil para alguém.

Fedaykin
fonte
3

Você poderia fazer isso com serviços ou fábricas. Eles são essencialmente os mesmos para algumas diferenças principais. Eu achei essa explicação no thinkster.io a mais fácil de seguir. Simples, direto ao ponto e eficaz.

Noahdecoco
fonte
1
"Você poderia fazer isso com serviços ou fábricas" - Como ..? Como fazer isso é o que o OP está pedindo ... publique uma resposta completa no próprio stackoverflow, em vez de vincular a recursos externos, os links podem ficar fora do tempo estipulado.
TJ
2

Você também não poderia tornar a propriedade parte do pai dos escopos?

$scope.$parent.property = somevalue;

Não estou dizendo que está certo, mas funciona.

SideFX
fonte
3
O autor afirmou isso NOTE: These controllers are not nested inside each other.. Se fossem controladores aninhados ou compartilhados com o mesmo pai, isso funcionaria, mas não podemos esperar isso.
Chris Foster
2
Geralmente, é uma prática ruim confiar $parentse isso puder ser evitado. Um componente reutilizável bem projetado não deve saber sobre seus pais.
Dmitri Zaitsev 02/09
2

Ah, tenha um pouco desse material novo como outra alternativa. É o armazenamento local e funciona onde o angular funciona. De nada. (Mas sério, obrigado o cara)

https://github.com/gsklee/ngStorage

Defina seus padrões:

$scope.$storage = $localStorage.$default({
    prop1: 'First',
    prop2: 'Second'
});

Acesse os valores:

$scope.prop1 = $localStorage.prop1;
$scope.prop2 = $localStorage.prop2;

Armazene os valores

$localStorage.prop1 = $scope.prop1;
$localStorage.prop2 = $scope.prop2;

Lembre-se de injetar ngStorage no seu aplicativo e $ localStorage no seu controlador.

kJamesy
fonte
1
Isso resolve um problema diferente - armazenamento persistente. Não é uma solução escalável para o problema em questão, pois faz com que seu código vaze com efeitos colaterais, como modificar o objeto de armazenamento local com vulnerabilidade de conflito de nome, entre outros.
Dmitri Zaitsev
1

Existem duas maneiras de fazer isso

1) Use o serviço de obtenção / configuração

2) $scope.$emit('key', {data: value}); //to set the value

 $rootScope.$on('key', function (event, data) {}); // to get the value
Rohan Kawade
fonte
1

Segunda Abordagem:

angular.module('myApp', [])
  .controller('Ctrl1', ['$scope',
    function($scope) {

    $scope.prop1 = "First";

    $scope.clickFunction = function() {
      $scope.$broadcast('update_Ctrl2_controller', $scope.prop1);
    };
   }
])
.controller('Ctrl2', ['$scope',
    function($scope) {
      $scope.prop2 = "Second";

        $scope.$on("update_Ctrl2_controller", function(event, prop) {
        $scope.prop = prop;

        $scope.both = prop + $scope.prop2; 
    });
  }
])

Html:

<div ng-controller="Ctrl2">
  <p>{{both}}</p>
</div>

<button ng-click="clickFunction()">Click</button>

Para mais detalhes, consulte plunker:

http://plnkr.co/edit/cKVsPcfs1A1Wwlud2jtO?p=preview

Codiee
fonte
1
Funciona apenas se Ctrl2(o ouvinte) for um controlador filho de Ctrl1. Os controladores irmãos precisam se comunicar via $rootScope.
Herzbube 19/05/19
0

Se você não deseja prestar serviços de manutenção, pode fazer assim.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
obrigado
fonte
37
Não duvido que essa resposta funcione, mas quero observar que isso contraria a filosofia do AngularJS de nunca ter objetos DOM em seu código de modelo / controlador.
precisa
3
-1 porque a comunicação do controlador via DOM é uma prática ruim, na minha opinião.
Chris Foster
3
@ ChrisFoster, apenas porque um martelo é vendido como uma "ferramenta", isso não significa que não pode ser usado como peso de papel. Tenho certeza de que, para cada estrutura ou ferramenta existente, você sempre encontrará desenvolvedores que precisam "dobrar" a lista de "melhores práticas".
Andrei V
5
@AndreiV - Pobre analogia, não há desvantagem em usar um martelo como peso de papel. Fazer más práticas como essa tem claras desvantagens e pode facilmente levar ao código de espaguete. O código acima é frágil porque agora depende de onde seu controlador está no DOM e é muito difícil de testar. O uso de um serviço é uma prática recomendada por um motivo, porque não vincula sua implementação ao seu modelo. Concordo que os desenvolvedores geralmente precisam dobrar a lista de práticas recomendadas, mas não quando há uma prática recomendada clara, comum e mais modular que funcione melhor.
Chris Foster
-1

Além de $ rootScope e serviços, há uma solução alternativa limpa e fácil de estender angular para adicionar os dados compartilhados:

nos controladores:

angular.sharedProperties = angular.sharedProperties 
    || angular.extend(the-properties-objects);

Essas propriedades pertencem ao objeto 'angular', separado dos escopos, e podem ser compartilhadas em escopos e serviços.

Um benefício é que você não precisa injetar o objeto: eles são acessíveis em qualquer lugar imediatamente após a sua definição!

williamjxj
fonte
2
Isto é como ter variáveis globais em todo windowobjeto ... Se você estiver indo para poluir angular, porque não basta ir em frente e poluem o objeto de janela ...
TJ