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.
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/
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);
}
});
There are many ways you can share the data between controllers
using services using $state.go services using stateparams using rootscope
Explanation of each method:
I am not going to explain as its already explained by someone using $state.go $state.go('book.name', {Name: 'XYZ'}); // then get parameter out of URL $state.params.Name; $stateparam works in a similar way to $state.go, you pass it as object from sender controller and collect in receiver controller using stateparam 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); });
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
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })
if you have ui.router
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
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>
FirstCtrl
is parent and gets a promise that SecondCtrl
(child) will also need? I don't want to make two api calls. Thanks.
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;
});
There are multiple ways to do this.
Events - already explained well. ui router - explained above. Service - with update method displayed above BAD - Watching for changes. 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();
}
}
});
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.
sharedDataEventHub.changeData(newData)
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.
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.
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 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.
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>
dummy
is global to both controllers rather than shared in a more modular way via inheritance with $scope or controllerAs or using a service.
Success story sharing
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{}" ?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#$watch