Share data between AngularJS controllers
Asked Answered
E

11

371

I'm trying to share data across controllers. Use-case is a multi-step form, data entered in one input is later used in multiple display locations outside the original controller. Code below and in jsfiddle here.

HTML

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="FirstName"><!-- Input entered here -->
    <br>Input is : <strong>{{FirstName}}</strong><!-- Successfully updates here -->
</div>

<hr>

<div ng-controller="SecondCtrl">
    Input should also be here: {{FirstName}}<!-- How do I automatically updated it here? -->
</div>

JS

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// make a factory to share data between controllers
myApp.factory('Data', function(){
    // I know this doesn't work, but what will?
    var FirstName = '';
    return FirstName;
});

// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

Any help is greatly appreciated.

Egon answered 20/2, 2014 at 21:27 Comment(1)
possible duplicate of How do I use $rootScope in Angular to store variables?Ferromagnetism
E
483

A simple solution is to have your factory return an object and let your controllers work with a reference to the same object:

JS:

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// Create the factory that share the Fact
myApp.factory('Fact', function(){
  return { Field: '' };
});

// Two controllers sharing an object that has a string in it
myApp.controller('FirstCtrl', function( $scope, Fact ){
  $scope.Alpha = Fact;
});

myApp.controller('SecondCtrl', function( $scope, Fact ){
  $scope.Beta = Fact;
});

HTML:

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="Alpha.Field">
    First {{Alpha.Field}}
</div>

<div ng-controller="SecondCtrl">
<input type="text" ng-model="Beta.Field">
    Second {{Beta.Field}}
</div>

Demo: http://jsfiddle.net/HEdJF/

When applications get larger, more complex and harder to test you might not want to expose the entire object from the factory this way, but instead give limited access for example via getters and setters:

myApp.factory('Data', function () {

    var data = {
        FirstName: ''
    };

    return {
        getFirstName: function () {
            return data.FirstName;
        },
        setFirstName: function (firstName) {
            data.FirstName = firstName;
        }
    };
});

With this approach it is up to the consuming controllers to update the factory with new values, and to watch for changes to get them:

myApp.controller('FirstCtrl', function ($scope, Data) {

    $scope.firstName = '';

    $scope.$watch('firstName', function (newValue, oldValue) {
        if (newValue !== oldValue) Data.setFirstName(newValue);
    });
});

myApp.controller('SecondCtrl', function ($scope, Data) {

    $scope.$watch(function () { return Data.getFirstName(); }, function (newValue, oldValue) {
        if (newValue !== oldValue) $scope.firstName = newValue;
    });
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="firstName">
  <br>Input is : <strong>{{firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{firstName}}
</div>

Demo: http://jsfiddle.net/27mk1n1o/

Ellisellison answered 20/2, 2014 at 21:42 Comment(12)
First solution worked best for me -- had the service maintain the object and the controllers just handle with the reference. Mucho thanks.Egon
the $scope.$watch method works beautifully for making a rest call from one scope and applying the results to another scopeProlocutor
Instead of returning an object like return { FirstName: '' }; or returning an object containing methods that interact with an object, would it not be prefferable to return an instance of a class(function) so that when you use your object, it has a specific type and it is not only an "Object{}" ?Remora
Just a heads up, the listener function doesn't need the newValue !== oldValue check because Angular doesn't execute the function if the oldValue and newValue are the same. The only exception to this is if you care about the initial value. See: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchVeratrine
@Ellisellison , It works good ,if I don't refresh the page . Can you suggest me any solution if I refresh the page ???Internist
To add to this answer, I would think of the service as the way to manage the common data. The controllers can use the data from the service and call service methods for operations (updates etc). A simple example can be found here: ozkary.com/2015/08/angularjs-sharing-data-across.htmlExurb
@tasseKaTT does that mean in order to able to share data , I have to $watch each single of them ?Rhombencephalon
If you go with the second solution and want to put the control on property level, yes. Honestly though, I have almost never used the second solution in any of the applications I've built and I will probably update this answer when I get time.Ellisellison
@Ellisellison please use more unique names in your illustrational code, so newcomers and beginners see what is what. Calling everything Data is confusing when learning, as someone can't know if it is a builtin data type, like the data variable returned from $http for example - or a variable that has been declared in different scopes. Please use some unofficially sounding unique names, eg. asdf, xyz ... instead of data.Gouveia
@KirályIstván The answerer has taken the time to answer. If you feel that the code can be improved please feel free to suggest edits to the code.Disposal
The second approach worked wonders for me! Thanks for a really nice explanationCalliopsis
What if the shared data is unknown at the point of creating the object? Let's say you need to make a user to users/*userId* so you create a function in the factory to do this. However you don't want to make the same call in both controllers and you need the data to be shared between them?Eicher
R
71

I prefer not to use $watch for this. Instead of assigning the entire service to a controller's scope you can assign just the data.

JS:

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

myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    }
    // Other methods or objects can go here
  };
});

myApp.controller('FirstCtrl', function($scope, MyService){
  $scope.data = MyService.data;
});

myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="data.firstName">
  <br>Input is : <strong>{{data.firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{data.firstName}}
</div>

Alternatively you can update the service data with a direct method.

JS:

// A new factory with an update method
myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    },
    update: function(first, last) {
      // Improve this method as needed
      this.data.firstName = first;
      this.data.lastName = last;
    }
  };
});

// Your controller can use the service's update method
myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;

   $scope.updateData = function(first, last) {
     MyService.update(first, last);
   }
});
Retiary answered 5/8, 2014 at 18:11 Comment(11)
You don't use explicitly watch() but internally AngularJS does to update the view with new values in $scope variables. In terms of performance it's the same or not?Gaffrigged
I'm not sure on the performance of either method. From what I understand Angular is already running a digest loop on the scope so setting up additional watch expressions probably adds more work for Angular. You would have to run a performance test to really find out. I just prefer data transfer and sharing via the answer above over updating via $watch expressions.Retiary
I don't get how this is an alternative to $watch. $watch is like a push to all watchers, that the data changes. You make manual updates which aren't usefull if you just want to update the master data in the factory. Or am I missing something?Danadanae
This is not an alternative to $watch. It is a different way to share data between two Angular objects, in this case controllers, without having to use $watch.Retiary
IMO this solution is the best one because it is more in the spirit of Angular's declaritive approach, as opposed to adding $watch statements, which feels a bit more jQuery-esque.Cosher
Why isn't this the most voted answer I wonder. After all $watch makes the code more complexBlouson
Agreed. I love the declarative ideal of this approach - especially since Todd Motto's opinionated style guide recommends limiting your use of $scope.$watch.Tonsillitis
@Retiary are there any issues to be aware of when using a data declaration like this as opposed to a nominal $scope.$watch when sharing data between Angular objects? This seems so elegant, i'm not sure why it's not more widely used...Tonsillitis
@Tonsillitis Not that I'm aware of. Anything that I inject a service into (controller, another service) is sharing the same singleton object (our injected service). So when the service object it updated it is immediately reflected in other objects that depend on it. I have found this to extremely reliable.Retiary
This is the best solution. Using adding $watches can rapidly lead to performance issues especially in an ng-repeat.Disposal
What if the two controllers are in two different files (components)? What do we do then to let the data from one controller to be aware of the data from the other?Judicious
W
12

There are many ways you can share the data between controllers

  1. using services
  2. using $state.go services
  3. using stateparams
  4. using rootscope

Explanation of each method:

  1. I am not going to explain as its already explained by someone

  2. using $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
    
  3. $stateparam works in a similar way to $state.go, you pass it as object from sender controller and collect in receiver controller using stateparam

  4. using $rootscope

    (a) sending data from child to parent controller

      $scope.Save(Obj,function(data) {
          $scope.$emit('savedata',data); 
          //pass the data as the second parameter
      });
    
      $scope.$on('savedata',function(event,data) {
          //receive the data as second parameter
      }); 
    

    (b) sending data from parent to child controller

      $scope.SaveDB(Obj,function(data){
          $scope.$broadcast('savedata',data);
      });
    
      $scope.SaveDB(Obj,function(data){`enter code here`
          $rootScope.$broadcast('saveCallback',data);
      });
    
Whalen answered 24/8, 2016 at 14:42 Comment(0)
D
8

There are multiple ways to share data between controllers

  • Angular services
  • $broadcast, $emit method
  • Parent to child controller communication
  • $rootscope

As we know $rootscope is not preferable way for data transfer or communication because it is a global scope which is available for entire application

For data sharing between Angular Js controllers Angular services are best practices eg. .factory, .service
For reference

In case of data transfer from parent to child controller you can directly access parent data in child controller through $scope
If you are using ui-router then you can use $stateParmas to pass url parameters like id, name, key, etc

$broadcast is also good way to transfer data between controllers from parent to child and $emit to transfer data from child to parent controllers

HTML

<div ng-controller="FirstCtrl">
   <input type="text" ng-model="FirstName">
   <br>Input is : <strong>{{FirstName}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
   Input should also be here: {{FirstName}}
</div>

JS

myApp.controller('FirstCtrl', function( $rootScope, Data ){
    $rootScope.$broadcast('myData', {'FirstName': 'Peter'})
});

myApp.controller('SecondCtrl', function( $rootScope, Data ){
    $rootScope.$on('myData', function(event, data) {
       $scope.FirstName = data;
       console.log(data); // Check in console how data is coming
    });
});

Refer given link to know more about $broadcast

Devlen answered 5/9, 2017 at 6:20 Comment(2)
Thanks. It Worked! In my case, i send only variable .i just put $rootScope.$broadcast('myData',$scope.var) in ControllerStrati
I was using a child controller and was trying to access my function with $scope. It didn't exist from child controller 2 while it did from child controller 1. Any idea why? Both children controllers are added in the same file too...Sold
D
6

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

Donaldson answered 19/9, 2014 at 2:0 Comment(1)
Use $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... }) if you have ui.routerArezzini
C
5

Simplest Solution:

I have used an AngularJS service.

Step1: I have created an AngularJS service named SharedDataService.

myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

Step2: Create two controllers and use the above created service.

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

Step3: Simply use the created controllers in the view.

<body ng-app="myApp">

<div ng-controller="FirstCtrl">
<input type="text" ng-model="Person.name">
<br>Input is : <strong>{{Person.name}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
Input should also be here: {{Person.name}}
</div>

</body>

To see working solution to this problem please press the link below

https://codepen.io/wins/pen/bmoYLr

.html file:

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="FirstCtrl">
    <input type="text" ng-model="Person.name">
    <br>Input is : <strong>{{Person.name}}</strong>
   </div>

<hr>

  <div ng-controller="SecondCtrl">
    Input should also be here: {{Person.name}}
  </div>

//Script starts from here

<script>

var myApp = angular.module("myApp",[]);
//create SharedDataService
myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
    }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
}]);

</script>


</body>
</html>
Cardholder answered 11/10, 2018 at 4:19 Comment(1)
this is very clear example for us new folk using angularJs. How would you share/pass the data from a parent/child relationship? FirstCtrl is parent and gets a promise that SecondCtrl (child) will also need? I don't want to make two api calls. Thanks.Radiothorium
M
1

There is another way without using $watch, using angular.copy:

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

myApp.factory('Data', function(){

    var service = {
        FirstName: '',
        setFirstName: function(name) {
            // this is the trick to sync the data
            // so no need for a $watch function
            // call this from anywhere when you need to update FirstName
            angular.copy(name, service.FirstName); 
        }
    };
    return service;
});


// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});
Macao answered 3/11, 2015 at 17:20 Comment(0)
W
1

There are multiple ways to do this.

  1. Events - already explained well.

  2. ui router - explained above.

  3. Service - with update method displayed above
  4. BAD - Watching for changes.
  5. Another parent child approach rather than emit and brodcast -

*

<superhero flight speed strength> Superman is here! </superhero>
<superhero speed> Flash is here! </superhero>

*

app.directive('superhero', function(){
    return {
        restrict: 'E',
        scope:{}, // IMPORTANT - to make the scope isolated else we will pollute it in case of a multiple components.
        controller: function($scope){
            $scope.abilities = [];
            this.addStrength = function(){
                $scope.abilities.push("strength");
            }
            this.addSpeed = function(){
                $scope.abilities.push("speed");
            }
            this.addFlight = function(){
                $scope.abilities.push("flight");
            }
        },
        link: function(scope, element, attrs){
            element.addClass('button');
            element.on('mouseenter', function(){
               console.log(scope.abilities);
            })
        }
    }
});
app.directive('strength', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addStrength();
        }
    }
});
app.directive('speed', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addSpeed();
        }
    }
});
app.directive('flight', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addFlight();
        }
    }
});
Wormwood answered 27/4, 2017 at 6:37 Comment(0)
E
0

Not sure where I picked up this pattern but for sharing data across controllers and reducing the $rootScope and $scope this works great. It is reminiscent of a data replication where you have publishers and subscribers. Hope it helps.

The Service:

(function(app) {
    "use strict";
    app.factory("sharedDataEventHub", sharedDataEventHub);

    sharedDataEventHub.$inject = ["$rootScope"];

    function sharedDataEventHub($rootScope) {
        var DATA_CHANGE = "DATA_CHANGE_EVENT";
        var service = {
            changeData: changeData,
            onChangeData: onChangeData
        };
        return service;

        function changeData(obj) {
            $rootScope.$broadcast(DATA_CHANGE, obj);
        }

        function onChangeData($scope, handler) {
            $scope.$on(DATA_CHANGE, function(event, obj) {
                handler(obj);
            });
        }
    }
}(app));

The Controller that is getting the new data, which is the Publisher would do something like this..

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

The Controller that is also using this new data, which is called the Subscriber would do something like this...

sharedDataEventHub.onChangeData($scope, function(data) {
    vm.localData.Property1 = data.Property1;
    vm.localData.Property2 = data.Property2;
});

This will work for any scenario. So when the primary controller is initialized and it gets data it would call the changeData method which would then broadcast that out to all the subscribers of that data. This reduces the coupling of our controllers to each other.

Euhemerism answered 2/9, 2015 at 20:20 Comment(7)
using a 'model' that shares the state between controllers seems like the option that is mostly used. Unfortunately this doesn't cover the scenario where you would refresh the child controller. How do you 'refetch' the data from the server and recreate the model ? And how would you deal with cache invalidation ?Ricercar
I think the idea of parent and child controllers is a bit dangerous and causes too many dependencies on each other. However, when used in combination with UI-Router, you can easily refresh the data on your "child", especially if that data is injected via the state configuration. Cache invalidation happens on the publisher whereby they would be responsible for retrieving the data and calling sharedDataEventHub.changeData(newData)Euhemerism
I think there is a misunderstanding. By refresh i mean the users literally press the F5 key on the keyboard and causes the browser to do a post back. If you are in the child/details view when this happens, there is no one to call "var someData = yourDataService.getSomeData()" . The question is, how would you deal with this scenario ? who should update the model ?Ricercar
Depending on how you activate your controllers this could be very simple. If you have a method that activates then the controller that is responsible for initially loading the data, would load it and then use the sharedDataEventHub.changeData(newData) then any child or controller that depends on that data would have somewhere in their controller body the following: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; }); If you used UI-Router this could be injected in from the state config.Euhemerism
Here is an example that ilustrates the problem that i am trying to describe: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . If you pop out the plunk, navigate to the edit page and refresh the browser, it will crash. Who should recreate the model in this case ?Ricercar
The page does not crash for me. Perhaps its browser specific. Also in an SPA a refresh should never crash your application. You are using UI Router, so why not inject your startup data into the controller with a resolve?Euhemerism
Let us continue this discussion in chat.Ricercar
N
0

As pointed out by @MaNn in one of the comments of the accepted answers, the solution wont work if the page is refreshed.

The Solution for this is to use localStorage or sessionStorage for temporary persistence of the data you want to share across controllers.

  1. Either you make a sessionService whose GET and SET method, encrypts and decrypts the data and reads the data from either localStorage or sessionStorage. So now you use this service directly to read and write the data in the storage via any controller or service you want. This is a open approach and easy one
  2. Else you make a DataSharing Service and use localStorage inside it - so that if the page is refreshed the service will try and check the storage and reply back via the Getters and Setters you have made public or private in this service file.
Neace answered 3/10, 2020 at 6:56 Comment(0)
N
-3

Just do it simple (tested with v1.3.15):

<article ng-controller="ctrl1 as c1">
    <label>Change name here:</label>
    <input ng-model="c1.sData.name" />
    <h1>Control 1: {{c1.sData.name}}, {{c1.sData.age}}</h1>
</article>
<article ng-controller="ctrl2 as c2">
    <label>Change age here:</label>
    <input ng-model="c2.sData.age" />
    <h1>Control 2: {{c2.sData.name}}, {{c2.sData.age}}</h1>
</article>

<script>
    var app = angular.module("MyApp", []);

    var dummy = {name: "Joe", age: 25};

    app.controller("ctrl1", function () {
        this.sData = dummy;
    });

    app.controller("ctrl2", function () {
        this.sData = dummy;
    });
</script>

November answered 1/6, 2015 at 19:22 Comment(1)
I think this was down-voted because it's not modular. dummy is global to both controllers rather than shared in a more modular way via inheritance with $scope or controllerAs or using a service.Mediation

© 2022 - 2024 — McMap. All rights reserved.