Passando dados entre controladores em Angular JS?

243

Eu tenho um controlador básico que exibe meus produtos,

App.controller('ProductCtrl',function($scope,$productFactory){
     $productFactory.get().success(function(data){
           $scope.products = data;
     });
});

Na minha opinião, estou exibindo esses produtos em uma lista

<ul>
    <li ng-repeat="product as products">
        {{product.name}}
    </li>
</ul

O que estou tentando fazer é que, quando alguém clica no nome do produto, tenho outra exibição chamada carrinho, onde esse produto é adicionado.

 <ul class="cart">
      <li>
          //click one added here
      </li>
      <li>
          //click two added here
      </li>
 </ul>

Então, minha dúvida aqui é: como passar esses produtos clicados do primeiro controlador para o segundo? Eu assumi que o carrinho deveria ser um controlador também.

Eu trato de evento de clique usando diretiva. Também sinto que eu deveria estar usando o serviço para alcançar a funcionalidade acima apenas não consigo descobrir como? porque o carrinho terá um número predefinido de produtos adicionados, pode ser 5/10, dependendo de qual usuário da página é. Então, eu gostaria de manter este genérico.

Atualizar:

Eu criei um serviço para transmitir e no segundo controlador eu o recebo. Agora a consulta é como atualizo o dom? Desde a minha lista para soltar o produto é bastante codificado.

kishanio
fonte

Respostas:

322

A partir da descrição, parece que você deveria estar usando um serviço. Confira http://egghead.io/lessons/angularjs-sharing-data-between-controllers e o serviço AngularJS passando dados entre controladores para ver alguns exemplos.

Você pode definir o serviço do produto (como uma fábrica) como tal:

app.factory('productService', function() {
  var productList = [];

  var addProduct = function(newObj) {
      productList.push(newObj);
  };

  var getProducts = function(){
      return productList;
  };

  return {
    addProduct: addProduct,
    getProducts: getProducts
  };

});

A dependência injeta o serviço nos dois controladores.

No seu ProductController, defina alguma ação que adicione o objeto selecionado à matriz:

app.controller('ProductController', function($scope, productService) {
    $scope.callToAddToProductList = function(currObj){
        productService.addProduct(currObj);
    };
});

No seu CartController, obtenha os produtos do serviço:

app.controller('CartController', function($scope, productService) {
    $scope.products = productService.getProducts();
});
Chalise
fonte
3
O controlador do carrinho será atualizado uma vez quando getProducts () for chamado? Ou sempre que um novo produto é adicionado?
Kishanio
1
Se você deseja atualizar automaticamente, pode adicionar a transmissão da resposta de Maxim Shoustin e chamar getProducts () na função $ on para atualizar o escopo do CartCtrl.
precisa
2
@krillgar Você está completamente correto, isso foi esquecido inicialmente. Editei a resposta para refletir uma solução funcional.
Chalise
1
@DeveshM Sim, faça o que você sugere. Convém estender os métodos para que eles não estejam apenas retendo dados na memória e sejam mais persistentes (salve no servidor via $httpchamada, por exemplo).
Chalise
1
O que acontece se eu tiver 2 controladores de produto e 2 carrinhos, por exemplo? Ambos terão o elemento adicionado? Como você resolve isso nesse caso?
atoth
67

como passar esses produtos clicados do primeiro controlador para o segundo?

Ao clicar, você pode chamar o método que chama a transmissão :

$rootScope.$broadcast('SOME_TAG', 'your value');

e o segundo controlador ouvirá essa tag como:

$scope.$on('SOME_TAG', function(response) {
      // ....
})

Como não podemos injetar $ scope em serviços, não há nada como um singleton $ scope.

Mas nós podemos injetar $rootScope. Portanto, se você armazenar valor no Serviço, poderá executar $rootScope.$broadcast('SOME_TAG', 'your value');no corpo do Serviço. (Consulte a descrição @Charx sobre serviços)

app.service('productService',  function($rootScope) {/*....*/}

Por favor, verifique um bom artigo sobre $ broadcast , $ emit

Maxim Shoustin
fonte
1
Sim, isso funciona como charme que estou usando de fábrica? Há apenas uma última coisa em que estou preso: eu recebo dados no novo controlador toda vez que clico no produto. Agora, como atualizá-lo no DOM? Porque eu já tenho permite lista digamos de 5 codificado com bordas de modo cada produto precisa ir para dentro deles
kishanio
1
@KT - Você já recebeu uma resposta? Parece uma pergunta óbvia, mas não consigo encontrar a resposta em lugar algum. Eu quero que um controlador altere algum valor. Quando esse valor do aplicativo muda, qualquer outro controlador de escuta deve se atualizar conforme necessário. Não consigo encontrar.
Raddevus
2
@ daylight a resposta no seu q. baseia-se em qual estrutura de controlador você possui: paralelo ou pai-filho. $rootScope.$broadcastnotifica todos os controladores que possuem estrutura paralela, ou seja, o mesmo nível.
Maxim Shoustin
@MS Obrigado pela resposta. Existe uma maneira de fazer isso usando $ watch? BTW, o meu são controladores paralelos e eu consegui funcionar usando seu método. Para mim, sempre que tento adicionar o $ watch (até o $ rootScope), o método associado ao $ watch no segundo controlador dispara apenas na primeira vez (inicialização do segundo controlador). Obrigado.
Raddevus
1
podemos usar $watchse o valor existir no $rootScopenível. Caso contrário, apenas a transmissão poderá notificar outros controladores. Para sua informação, se outro programador vir em seu código broadcast- a lógica é clara o que você tenta fazer - "evento de transmissão". De qualquer forma, da minha exp. não é boa prática usar rootscopepara watch. Sobre "acionar apenas a 1ª vez": veja este exemplo: plnkr.co/edit/5zqkj7iYloeHYkzK1Prt?p=preview você tem valores antigos e novos. certifique-se de que cada vez que você começa novo valor que does not igual a um velho
Maxim Shoustin
24

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
27
$rootScopedeve ser evitado o máximo possível.
Shaz
1
@ Shaz você poderia elaborar por quê?
Mirko
5
@Mirko Você deve evitar o estado global o máximo possível, porque qualquer pessoa pode alterá-lo, tornando o estado do programa imprevisível.
Umut Sete
4
$ rootScope tem escopo global, então, como variáveis ​​globais nativas, temos que usá-las com cuidado. Se algum controlador alterar seus valores, ele será alterado para o escopo global.
Sanjeev
1
Os Serviços e Fábricas são Singleton, mas você pode optar por injetar ou não qualquer serviço em seu Controlador. Mas todo o escopo filho deriva do rootcope e segue a cadeia prototípica. Portanto, se você tentar acessar uma propriedade no seu escopo e ela não estiver presente, ela pesquisará a cadeia. Existe a possibilidade de você alterar indesejadamente a propriedade do rootcope.
Sanjeev
16

Você pode fazer isso por dois métodos.

  1. Ao usar $rootscope, mas não recomendo isso. O $rootScopeé o escopo mais alto. Um aplicativo pode ter apenas um $rootScopeque será compartilhado entre todos os componentes de um aplicativo. Portanto, ele atua como uma variável global.

  2. Usando serviços. Você pode fazer isso compartilhando um serviço entre dois controladores. O código de serviço pode ser assim:

    app.service('shareDataService', function() {
        var myList = [];
    
        var addList = function(newObj) {
            myList.push(newObj);
        }
    
        var getList = function(){
            return myList;
        }
    
        return {
            addList: addList,
            getList: getList
        };
    });

    Você pode ver meu violino aqui .

Nisham Mahsin
fonte
e uma 3ª opção, você pode enviar evento, apenas para adicionar na lista
DanteTheSmith
E SE O USUÁRIO ATUALIZAR A PÁGINA? ENTÃO getProducts () dará nada (U não pode dizer a todos os utilizadores não de atualização, pode u?)!
Shreedhar bhat
2
@shreedharbhat A questão não é perguntar sobre a persistência de dados nos recarregamentos de páginas.
Jack Vial
9

Uma maneira ainda mais simples de compartilhar os dados entre controladores é usar estruturas de dados aninhadas. Em vez de, por exemplo

$scope.customer = {};

podemos usar

$scope.data = { customer: {} };

A datapropriedade será herdada do escopo pai, para que possamos sobrescrever seus campos, mantendo o acesso de outros controladores.

Bartłomiej Zalewski
fonte
1
Os controladores herdam as propriedades do escopo dos controladores pai para seu próprio escopo. Limpar \ limpo! simplicidade faz com que seja bonita
Carlos R Balebona
Não posso fazê-lo funcionar. no segundo controlador eu uso $ scope.data e é indefinido.
Bart Calixto
Ele está falando sobre controladores aninhados, eu acho. Também não me serve muito.
Norbert Norbertson
8
angular.module('testAppControllers', [])
    .controller('ctrlOne', function ($scope) {
        $scope.$broadcast('test');
    })
    .controller('ctrlTwo', function ($scope) {
        $scope.$on('test', function() {
        });
    });
Jijo Paulose
fonte
ele usa mais
Leathan
5

podemos armazenar dados em sessão e usá-los em qualquer lugar do programa.

$window.sessionStorage.setItem("Mydata",data);

Outro lugar

$scope.data = $window.sessionStorage.getItem("Mydata");
Alex Kumbhani
fonte
4

Eu vi as respostas aqui e ele está respondendo à questão do compartilhamento de dados entre controladores, mas o que devo fazer se quiser que um controlador notifique o outro sobre o fato de que os dados foram alterados (sem usar a transmissão)? FÁCIL! Apenas usando o famoso padrão de visitante:

myApp.service('myService', function() {

    var visitors = [];

    var registerVisitor = function (visitor) {
        visitors.push(visitor);
    }

    var notifyAll = function() {
        for (var index = 0; index < visitors.length; ++index)
            visitors[index].visit();
    }

    var myData = ["some", "list", "of", "data"];

    var setData = function (newData) {
        myData = newData;
        notifyAll();
    }

    var getData = function () {
        return myData;
    }

    return {
        registerVisitor: registerVisitor,
        setData: setData,
        getData: getData
    };
}

myApp.controller('firstController', ['$scope', 'myService',
    function firstController($scope, myService) {

        var setData = function (data) {
            myService.setData(data);
        }

    }
]);

myApp.controller('secondController', ['$scope', 'myService',
    function secondController($scope, myService) {

        myService.registerVisitor(this);

        this.visit = function () {
            $scope.data = myService.getData();
        }

        $scope.data = myService.getData();
    }
]);

Dessa maneira simples, um controlador pode atualizar outro controlador que alguns dados foram atualizados.

Omri Lahav
fonte
2
O padrão de observador ( en.wikipedia.org/wiki/Observer_pattern ) não seria mais relevante aqui? Padrão do visitante é outra coisa ... en.wikipedia.org/wiki/Visitor_pattern
Ant Kutschera
3

Criei uma fábrica que controla o escopo compartilhado entre o padrão do caminho da rota, para que você possa manter os dados compartilhados apenas quando os usuários estiverem navegando no mesmo caminho pai da rota.

.controller('CadastroController', ['$scope', 'RouteSharedScope',
    function($scope, routeSharedScope) {
      var customerScope = routeSharedScope.scopeFor('/Customer');
      //var indexScope = routeSharedScope.scopeFor('/');
    }
 ])

Portanto, se o usuário for para outro caminho de rota, por exemplo '/ Support', os dados compartilhados para o caminho '/ Customer' serão automaticamente destruídos. Mas se, em vez disso, o usuário seguir para caminhos 'filho', como '/ Customer / 1' ou '/ Customer / list', o escopo não será destruído.

Você pode ver uma amostra aqui: http://plnkr.co/edit/OL8of9

Oberdan Nunes
fonte
3

1

using $localStorage

app.controller('ProductController', function($scope, $localStorage) {
    $scope.setSelectedProduct = function(selectedObj){
        $localStorage.selectedObj= selectedObj;
    };
});

app.controller('CartController', function($scope,$localStorage) { 
    $scope.selectedProducts = $localStorage.selectedObj;
    $localStorage.$reset();//to remove
});

2

Ao clicar, você pode chamar o método que chama a transmissão:

$rootScope.$broadcast('SOME_TAG', 'your value');

and the second controller will listen on this tag like:
$scope.$on('SOME_TAG', function(response) {
      // ....
})

3

using $rootScope:

4

window.sessionStorage.setItem("Mydata",data);
$scope.data = $window.sessionStorage.getItem("Mydata");

5

Uma maneira de usar o serviço angular:

var app = angular.module("home", []);

app.controller('one', function($scope, ser1){
$scope.inputText = ser1;
});

app.controller('two',function($scope, ser1){
$scope.inputTextTwo = ser1;
});

app.factory('ser1', function(){
return {o: ''};
});
Subhransu
fonte
2
coloque mais algumas explicações em sua resposta.
Gerhard Barnard
2

Faça uma fábrica no seu módulo e adicione uma referência da fábrica no controlador e use suas variáveis ​​no controlador e agora obtenha o valor dos dados em outro controlador adicionando referência onde quer que você queira

Resham Kadel
fonte
2

Não sei se isso ajudará alguém, mas com base na resposta do Charx (obrigado!), Criei um serviço de cache simples. Sinta-se livre para usar, remixar e compartilhar:

angular.service('cache', function() {
    var _cache, _store, _get, _set, _clear;
    _cache = {};

    _store = function(data) {
        angular.merge(_cache, data);
    };

    _set = function(data) {
        _cache = angular.extend({}, data);
    };

    _get = function(key) {
        if(key == null) {
            return _cache;
        } else {
            return _cache[key];
        }
    };

    _clear = function() {
        _cache = {};
    };

    return {
        get: _get,
        set: _set,
        store: _store,
        clear: _clear
    };
});
marverix
fonte
2

Uma maneira de usar o serviço angular:

var app = angular.module("home", []);

app.controller('one', function($scope, ser1){
$scope.inputText = ser1;
});


app.controller('two',function($scope, ser1){
$scope.inputTextTwo = ser1;
});

app.factory('ser1', function(){
return {o: ''};
});



<div ng-app='home'>

<div ng-controller='one'>
  Type in text: 
  <input type='text' ng-model="inputText.o"/>
</div>
<br />

<div ng-controller='two'>
  Type in text:
  <input type='text' ng-model="inputTextTwo.o"/>
</div>

</div>

https://jsfiddle.net/1w64222q/

Siva ganesh
fonte
1

Para sua informação, o objeto $ scope tem o $ emit, $ broadcast, $ on E o objeto $ rootScope possui o idêntico $ emit, $ broadcast, $ on

leia mais sobre o padrão de design de publicação / assinatura em angular aqui

Anja Ishmukhametova
fonte
1

Para melhorar a solução proposta pelo @Maxim usando $ broadcast, envie os dados não mudam

$rootScope.$broadcast('SOME_TAG', 'my variable');

mas ouvindo dados

$scope.$on('SOME_TAG', function(event, args) {
    console.log("My variable is", args);// args is value of your variable
})
Cedriga
fonte
1

Existem três maneiras de fazer isso,

a) usando um serviço

b) Explorar a relação pai / filho dependente entre os escopos do controlador.

c) No Angular 2.0, a palavra-chave "As" passará os dados de um controlador para outro.

Para mais informações, por exemplo, verifique o link abaixo:

http://www.tutespace.com/2016/03/angular-js.html

Shrinivas Kalangutkar
fonte
0
var custApp = angular.module("custApp", [])
.controller('FirstController', FirstController)
.controller('SecondController',SecondController)
.service('sharedData', SharedData);

FirstController.$inject = ['sharedData'];
function FirstController(sharedData) {
this.data = sharedData.data;
}

SecondController.$inject['sharedData'];
function SecondController(sharedData) {
this.data = sharedData.data;
}

function SharedData() {
this.data = {
    value: 'default Value'
}
}

Primeiro Controlador

<div ng-controller="FirstController as vm">
<input type=text ng-model="vm.data.value" />
</div>

Segundo Controlador

 <div ng-controller="SecondController as vm">
    Second Controller<br>
    {{vm.data.value}}
</div>
testmeon
fonte
-1

Eu acho que o

melhor maneira

é usar $ localStorage. (Funciona o tempo todo)

app.controller('ProductController', function($scope, $localStorage) {
    $scope.setSelectedProduct = function(selectedObj){
        $localStorage.selectedObj= selectedObj;
    };
});

O seu controlador de cartão será

app.controller('CartController', function($scope,$localStorage) { 
    $scope.selectedProducts = $localStorage.selectedObj;
    $localStorage.$reset();//to remove
});

Você também pode adicionar

if($localStorage.selectedObj){
    $scope.selectedProducts = $localStorage.selectedObj;
}else{
    //redirect to select product using $location.url('/select-product')
}
shreedhar bhat
fonte