251

I have a basic controller that displays my products,

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

In my view I'm displaying this products in a list

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

What I'm trying to do is when someone click on the product name, i have another view named cart where this product is added.

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

So my doubt here is, how do pass this clicked products from first controller to second? i assumed that cart should be a controller too.

I handle click event using directive. Also i feel i should be using service to achieve above functionality just can't figure how? because cart will be predefined number of products added could be 5/10 depending on which page user is. So i would like to keep this generic.

Update:

I created a service to broadcast and in the second controller i receive it. Now the query is how do i update dom? Since my list to drop product is pretty hardcoded.

kishanio
  • 6,979
  • 8
  • 25
  • 33
  • 1
    Possible duplicate of [Pass variables to AngularJS controller, best practice?](http://stackoverflow.com/questions/11703477/pass-variables-to-angularjs-controller-best-practice) – T.Todua Sep 20 '16 at 11:29

18 Answers18

325

From the description, seems as though you should be using a service. Check out http://egghead.io/lessons/angularjs-sharing-data-between-controllers and AngularJS Service Passing Data Between Controllers to see some examples.

You could define your product service (as a factory) as such:

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

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

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

  return {
    addProduct: addProduct,
    getProducts: getProducts
  };

});

Dependency inject the service into both controllers.

In your ProductController, define some action that adds the selected object to the array:

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

In your CartController, get the products from the service:

app.controller('CartController', function($scope, productService) {
    $scope.products = productService.getProducts();
});
Chalise
  • 3,656
  • 1
  • 24
  • 37
  • 4
    Cart controller will update once when getProducts() is called? Or every time new product is added? – kishanio Nov 24 '13 at 22:02
  • 1
    If you want it to automatically update, you can add the broadcast from Maxim Shoustin's answer and call getProducts() within the $on function to update the CartCtrl's scope. – Chalise Nov 24 '13 at 22:21
  • In order to get this set up to work for me, I needed to add a `return` to the `app.service('productService', function() { });` in order to expose those properties and methods so they could be used in the other controllers. – krillgar Jun 06 '14 at 15:44
  • 2
    @krillgar You are completely correct, that was overlooked initially. I've edited the answer to reflect a working solution. – Chalise Jun 06 '14 at 19:27
  • I have posted a solution below to create global variables without creating Service, but instead using $rootScope. – Sanjeev Aug 30 '14 at 15:25
  • I have a doubt. Suppose, i have one more object which need to share between two controllers apart from productList. In this case, do i need to create another methods for the new object like addNewObj/getNewObj OR is there any other ways ? Please help. – Devesh M Aug 20 '15 at 10:22
  • 1
    @DeveshM Yes, do as you suggest. You may want to consider extending the methods so they are not just holding data in memory and to be more persistent (save to server via `$http` call, for example). – Chalise Aug 24 '15 at 00:26
  • 1
    What happens if I have 2 productControllers and 2 carts for example? Both will have the element added? How do you solve that in this case? – atoth Feb 25 '16 at 15:28
  • @atoth Can you elaborate? If you'd like, I've opened a chat here: https://chat.stackoverflow.com/rooms/info/107039/discussing-question-on-http-stackoverflow-com-a-20181543-651952?tab=general – Chalise Mar 22 '16 at 13:06
  • 1
    Wouldn't that solution work more for a Factory, rather than a Service? Instead, should you be using this.something instead of var something =... ? That would remove the need to do a return object at the end. – Caleb Jay Apr 02 '16 at 19:12
  • WHAT IF THE USER REFRESH THE PAGE? THEN getProducts() WILL GIVE YOU NOTHING!(U can't tell all users not to refresh) – shreedhar bhat Jul 28 '17 at 06:55
  • @shreedharbhat Correct. This is purely a solution for passing data around in memory, as there is no persistence at all. You could add something that persists to local storage or even something more permanent in the service functions if you wish. – Chalise Oct 23 '17 at 16:21
  • hey @Chalise. Your answer worked for me, but as a factory and not a service. But I don't understand why it worked only after I changed `.service` to `.factory`. Any possible reason why? – Harshith Rai Jan 23 '19 at 09:16
  • @Rai You are right - it should be using a factory because I am returning an object literal of the get/add functions. Services are different and would expect these functions to be set directly on `this`. I've updated the code in the answer. – Chalise Feb 13 '19 at 22:21
67

how do pass this clicked products from first controller to second?

On click you can call method that invokes broadcast:

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

and the second controller will listen on this tag like:

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

Since we can't inject $scope into services, there is nothing like a singleton $scope.

But we can inject $rootScope. So if you store value into the Service, you can run $rootScope.$broadcast('SOME_TAG', 'your value'); in the Service body. (See @Charx description about services)

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

Please check good article about $broadcast, $emit

Anja Ishmukhametova
  • 1,535
  • 16
  • 14
Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
  • 1
    Yup this works like charm I'm using factory? There is just one last thing where I'm stuck i do get data in new controller every time i click product. Now how do i update it in DOM? Because i already have lets say list of 5 hardcoded with borders so each products need to go inside them – kishanio Nov 24 '13 at 22:47
  • 2
    @KT - Did you ever get an answer? It seems like an obvious question but I can't find the answer anywhere. I want a controller to change some value. When that app value changes then any other listening controllers should update themselves as necessary. Can't find it. – raddevus Feb 28 '14 at 18:52
  • 3
    @daylight the answer on your q. is based on what controller structure you have: parallel or child-parent. `$rootScope.$broadcast` notifies all controllers that have parallel structure aka the same level. – Maxim Shoustin Feb 28 '14 at 19:33
  • @MS Thanks for the reply. Is there a way to do this using $watch? BTW, Mine are parallel controllers and I got it to work using your method. For me, whenever I try to add the $watch (even to $rootScope) the method associated with the $watch in the 2nd controller would only fire the first time (initialization of the 2nd controller). Thanks. – raddevus Feb 28 '14 at 20:34
  • 1
    we can use `$watch` if the value exists on `$rootScope` level. Otherwise only broadcast might notify other controllers. FYI, if other programmer sees in your code `broadcast` - the logic is clear what you try to do - "broadcast event". Anyways, from my exp. its not good practice to use `rootscope` for `watch`. About "fire 1st time only": take a look on this example: http://plnkr.co/edit/5zqkj7iYloeHYkzK1Prt?p=preview you have old and new values. be sure that each time you get new value that doesnt equal to old one – Maxim Shoustin Feb 28 '14 at 22:32
  • @LKM what exactly doesn't work? Further, if some use-case doesn't work its not the reason to downvote. Downvoting means - from your view the answer is wrong – Maxim Shoustin Jan 04 '16 at 13:15
  • @MaximShoustin First, I think I have to answer your question, '$scope.$on' doesn't execute and sorry for misunderstanding. I update your question, please check this out and understanding my immaturity – LKM Jan 05 '16 at 06:53
  • WHAT IF THE USER REFRESH THE PAGE? THEN getProducts() WILL GIVE YOU NOTHING!(U can't tell all users not to refresh, can u?) – shreedhar bhat Jul 28 '17 at 06:59
  • @shreedharbhat how its related to `$broadcast`? on page refresh you call again `app.run` a.e. all angular engine. :) – Maxim Shoustin Jul 28 '17 at 07:02
  • @MaximShoustin When you are in the second controller, your second controller template will be loaded. Now you have the $broadcast value, and everything is fine. At this point user refresh the page! Now no value set to broadcast! – shreedhar bhat Jul 28 '17 at 07:18
  • @shreedharbhat well, its a different story, when u play with routing, broadcast should work by different way and its not related to SO question – Maxim Shoustin Jul 28 '17 at 08:03
  • @MaximShoustin I worked in an application used by public. Faced similar issue. That's why, I think $localStrorage will come in handy. https://stackoverflow.com/a/45367219/5902146 – shreedhar bhat Jul 28 '17 at 08:42
26

Solution without creating Service, using $rootScope:

To share properties across app Controllers you can use Angular $rootScope. This is another option to share data, putting it so that people know about it.

The preferred way to share some functionality across Controllers is Services, to read or change a global property you can use $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;
}]);

Using $rootScope in a template (Access properties with $root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
  • 2,607
  • 22
  • 16
  • 28
    `$rootScope` should be avoided as much as possible. – Shaz Sep 10 '14 at 15:09
  • 1
    @Shaz could you elaborate why? – Paranoid Android Sep 11 '14 at 14:17
  • 5
    @Mirko You should try to avoid global state as much as you can because anyone can change it -making your program state unpredictable. – Umut Seven Sep 12 '14 at 12:36
  • There are two ways to share data between controllers: 1) Factories/Services 2) $rootScope - All other scopes are descendant scopes of the root scope. The preferred way to share some functionality across Controllers is Services, to read or change a global property you can use $rootscope. I added this so that people know about its usage in controller/views. This is a feature provided by angular and i have not read anywhere that we should not use it, you have to decide as per your case. For me this was the best option and it worked pretty well. – Sanjeev Sep 15 '14 at 05:26
  • Is any reasion to avoid the `$rootScope` – vamsikrishnamannem Nov 26 '14 at 08:13
  • 4
    $rootScope has global scope, so like native global variables we have to use them carefully. If any controller changes its values, it will get changed for global scope. – Sanjeev Dec 07 '14 at 06:59
  • 1
    Services & Factories both are Singleton , but you can choose to inject or not any service in your Controller. But all child scope derives from rootscope and follow prototypical chain. So if you try to access a property on your scope and its not present then it will look up the chain. There is possibility that you unwantedly may change rootscope property. – Sanjeev May 31 '15 at 14:19
  • but what happens if I am getting value through service in 2nd controller and I reload that page then service will not return me any value because of reload. What would be solution for that? – Rakesh Chand Jul 07 '16 at 13:27
  • 1
    When you need to pass some data across controllers, which is unlikely to be changed by some other controllers, using `$rootScope` is a much more viable option, that create/modify a service. And I dare to say it is even less error-prone. – Neurotransmitter Dec 23 '16 at 11:38
  • 1
    WHAT IF THE USER REFRESH THE PAGE? THEN getProducts() WILL GIVE YOU NOTHING!(U can't tell all users not to refresh, can u?) – shreedhar bhat Jul 28 '17 at 06:57
16

You can do this by two methods.

  1. By using $rootscope, but I don't reccommend this. The $rootScope is the top-most scope. An app can have only one $rootScope which will be shared among all the components of an app. Hence it acts like a global variable.

  2. Using services. You can do this by sharing a service between two controllers. Code for service may look like this:

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

    You can see my fiddle here.

T J
  • 42,762
  • 13
  • 83
  • 138
Nisham Mahsin
  • 1,399
  • 5
  • 19
  • 43
9

An even simpler way to share the data between controllers is using nested data structures. Instead of, for example

$scope.customer = {};

we can use

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

The data property will be inherited from parent scope so we can overwrite its fields, keeping the access from other controllers.

Liglo App
  • 3,719
  • 4
  • 30
  • 54
8
angular.module('testAppControllers', [])
    .controller('ctrlOne', function ($scope) {
        $scope.$broadcast('test');
    })
    .controller('ctrlTwo', function ($scope) {
        $scope.$on('test', function() {
        });
    });
Jijo Paulose
  • 1,896
  • 18
  • 20
7

I saw the answers here, and it is answering the question of sharing data between controllers, but what should I do if I want one controller to notify the other about the fact that the data has been changed (without using broadcast)? EASY! Just using the famous visitor pattern:

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

In this simple manner, one controller can update another controller that some data has been updated.

Jeremy
  • 1,023
  • 3
  • 18
  • 33
Omri Lahav
  • 71
  • 1
  • 1
5

we can store data in session and can use it anywhere in out program.

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

Other place

$scope.data = $window.sessionStorage.getItem("Mydata");
Alex Kumbhani
  • 125
  • 2
  • 11
4

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

On click you can call method that invokes broadcast:

$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

One way using angular service:

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
  • 41
  • 1
3

I've created a factory that controls shared scope between route path's pattern, so you can maintain the shared data just when users are navigating in the same route parent path.

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

So, if the user goes to another route path, for example '/Support', the shared data for path '/Customer' will be automatically destroyed. But, if instead of this the user goes to 'child' paths, like '/Customer/1' or '/Customer/list' the the scope won't be destroyed.

You can see an sample here: http://plnkr.co/edit/OL8of9

Oberdan Nunes
  • 509
  • 4
  • 13
3

I don't know if it will help anyone, but based on Charx (thanks!) answer I have created simple cache service. Feel free to use, remix and share:

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
  • 7,184
  • 6
  • 38
  • 50
2

Make a factory in your module and add a reference of the factory in controller and use its variables in the controller and now get the value of data in another controller by adding reference where ever you want

2

One way using angular service:

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/

1

FYI The $scope Object has the $emit, $broadcast, $on AND The $rootScope Object has the identical $emit, $broadcast, $on

read more about publish/subscribe design pattern in angular here

Anja Ishmukhametova
  • 1,535
  • 16
  • 14
1

There are three ways to do it,

a) using a service

b) Exploiting depending parent/child relation between controller scopes.

c) In Angular 2.0 "As" keyword will be pass the data from one controller to another.

For more information with example, Please check the below link:

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

1

To improve the solution proposed by @Maxim using $broadcast, send data don't change

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

but to listening data

$scope.$on('SOME_TAG', function(event, args) {
    console.log("My variable is", args);// args is value of your variable
})
Cedriga
  • 3,860
  • 2
  • 28
  • 21
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'
}
}

First Controller

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

Second Controller

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

I think the

best way

is to use $localStorage. (Works all the time)
app.controller('ProductController', function($scope, $localStorage) {
    $scope.setSelectedProduct = function(selectedObj){
        $localStorage.selectedObj= selectedObj;
    };
});

Your cardController will be

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

You can also add

if($localStorage.selectedObj){
    $scope.selectedProducts = $localStorage.selectedObj;
}else{
    //redirect to select product using $location.url('/select-product')
}
shreedhar bhat
  • 4,779
  • 1
  • 18
  • 23