AngularJS Share data between Controllers when variable is set on another Controller - angularjs

I need to share data between two controller (controller A and controller B). I have a service in place to share the data. This works and controllerB can see the value that controllerA sets if I'm setting the value of the shared variable using "ng-model=shared.id" from the view, however; if I set the shared variable inside of the controllerA, then controllerB doesn't see it.
app['controller']('controllerA', ['shared', function(shared){
//ControllerB will not see this for some reason, unless I set
//the value from the view using the ng-model=shared.id attribute.
shared['id'] = "123";
}]);
app['controller']('controllerB', ['shared', function(shared){
this['someId'] = shared['id'];
}]);

Your code will change the service value without any issues. But the issue is controllerB won't be aware of the value change as there is no $digest cycle called.
When you change the value of $scope model, $digest loop will fire and the change carried forward to controllerB.
You can try as below,
app['controller']('controllerA', ['$scope', 'shared', function($scope, shared){
$scope.shared = shared;
//ControllerB will not see this for some reason, unless I set
//the value from the view using the ng-model=shared.id attribute.
$scope.shared['id'] = "123"; // This will trigger the $digest cycle
}]);
app['controller']('controllerB', ['shared', function(shared){
this['someId'] = shared['id'];
}]);

When the shared data is updated in the service object from one Controller the other Controller cannot know it automatically, there has to be some way to convey such as "Hey, I've updated the shared data if anybody is using it please go ahead and fetch the updated data", this can be done using events in AngularJS by using $broadcast, $on, $emit functions on the $scope object and on the $rootScope service.
Check the below example using $broadcast function on the $rootScope to invoke an event (such as the shared id got changed) on all the child scopes (i.e. broadcast will send a message to all the child scopes), the child scopes interested in an event will listen for that event using the $on function.
angular
.module('demo', [])
.controller('ControllerA', ControllerA)
.controller('ControllerB', ControllerB)
.factory('shared', shared);
ControllerA.$inject = ['$rootScope', 'shared'];
function ControllerA($rootScope, shared) {
var vm = this;
shared.id = 123;
vm.id = shared.id;
vm.updateId = updateId;
function updateId(id) {
shared.id = id;
$rootScope.$broadcast('idChanged');
}
}
ControllerB.$inject = ['$scope', 'shared'];
function ControllerB($scope, shared) {
var vm = this;
vm.id = shared.id;
$scope.$on('idChanged', idChanged);
function idChanged(event) {
vm.id = shared.id;
}
}
function shared() {
var service = {
id: 0
};
return service;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="ControllerA as ctrlA">
Controller A: {{ctrlA.id}}
<input type="text" name="id" ng-model="ctrlA.id" ng-change="ctrlA.updateId(ctrlA.id)" />
</div>
<div ng-controller="ControllerB as ctrlB">
Controller B: {{ctrlB.id}}
</div>
</div>

Related

Why can't I inject $scope into a factory in Angular?

I have a factory that needs to listen for a broadcast event. I injected $scope into the factory so I could use $scope.$on. But as soon as I add $scope to the parameter list I get an injector error.
This works fine:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$rootScope', function($rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
This throws an injector error:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$scope', '$rootScope', function($scope, $rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
Why can't I inject $scope into a factory? And if I can't, do I have any way of listening for events other than using $rootScope?
Because $scope is used for connecting controllers to view, factories are not really meant to use $scope.
How ever you can broadcast to rootScope.
$rootScope.$on()
Even though you can't use $scope in services, you can use the service as a 'store'. I use the following approach inspired on AltJS / Redux while developing apps on ReactJS.
I have a Controller with a scope which the view is bound to. That controller has a $scope.state variable that gets its value from a Service which has this.state = {}. The service is the only component "allowed" (by you, the developer, this a rule we should follow ourselves) to touch the 'state'.
An example could make this point a bit more clear
(function () {
'use strict';
angular.module('app', ['app.accounts']);
// my module...
// it can be defined in a separate file like `app.accounts.module.js`
angular.module('app.accounts', []);
angular.module('app.accounts')
.service('AccountsSrv', [function () {
var self = this;
self.state = {
user: false
};
self.getAccountInfo = function(){
var userData = {name: 'John'}; // here you can get the user data from an endpoint
self.state.user = userData; // update the state once you got the data
};
}]);
// my controller, bound to the state of the service
// it can be defined in a separate file like `app.accounts.controller.js`
angular.module('app.accounts')
.controller('AccountsCtrl', ['$scope', 'AccountsSrv', function ($scope, AccountsSrv) {
$scope.state = AccountsSrv.state;
$scope.getAccountInfo = function(){
// ... do some logic here
// ... and then call the service which will
AccountsSrv.getAccountInfo();
}
}]);
})();
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="AccountsCtrl">
Username: {{state.user.name ? state.user.name : 'user info not available yet. Click below...'}}<br/><br/>
Get account info
</div>
</div>
The benefit of this approach is you don't have to set $watch or $on on multiple places, or tediously call $scope.$apply(function(){ /* update state here */ }) every time you need to update the controller's state. Also, you can have multiple controllers talk to services, since the relationship between components and services is one controller can talk to one or many services, the decision is yours. This approach focus on keeping a single source of truth.
I've used this approach on large scale apps... it has worked like a charm.
I hope it helps clarify a bit about where to keep the state and how to update it.

How to change parent controller's object instance using an AngularJS service?

I'm using nested controllers and UI-Router. My top level controller, called MainCtrl, is set in my app's index.html file. If the MainCtrl uses a service, to pass data around, how can I change an instance of an object in the MainCtrl from a child controller without using $scope?
This is basically what I have (typed from memory):
var mainCtrl = function (ProfileSvc) {
var vm = this;
vm.profile = ProfileSvc.profile;
};
var loginCtrl = function (ProfileSvc, AuthSvc) {
var vm = this;
vm.doLogin = function (form) {
if (form.$error) { return; }
AuthSvc.login(form.user, form.pass).
.then(function(response) {
ProfileSvc.profile = response.data.profile;
}, function(errResponse) {
// error
}
};
};
User #shershen posted a reply to another question that gave me the idea to use $scope.$on and an event, however I really do not want references to $scope in my code:
Propagating model changes to a Parent Controller in Angular
I think without using $scope you may want to use the Controller as ctrl in your views. So...
var mainCtrl = function (ProfileSvc) {
var vm = this;
vm.profile = ProfileSvc.profile;
vm.updateProfile = function(profileAttrs) {
vm.profile = ProfileSvc.update(profileAttrs);
}
};
Then in the view, something along the lines of:
<div ng-controller="mainCtrl as main">
<button ng-click="main.updateProfile({ name: 'Fishz' })">
</div>
Hope this helps!
I had to do something similar on a project and ended up using $cacheFactory. First just load it up as a service with something like:
myApp.factory('appCache', function($cacheFactory) {
return $cacheFactory('appCache');
});
Then make sure you inject appCache into your controllers and then in your controllers you can call the cache service's put and get methods to store and retrieve your object.
In my case the parent view and child view both can change the object I'm caching, but the user only can commit from the parent.

how can I access a variable defined in one controller from the scope of another controller?

I have the following controllers:
HeaderCtrl, NewsFeedCtrl, MainCtrl
MainCtrl contains both the other two controllers, which are in the same level.
I'm defining an object in authenticationService and update its value in MainCtrl and I update it frequently in NewsFeedCtrl and I want to display its value in the HTML page controlled by HeaderCtrl.
when I use this line in my HeaderCtrl:
$scope.unreadNum=authenticationService.notificationList.length;
and then I use data binding in my HTML page to display its value:
{{unreadNum}}
I only get the initial value I inserted in authenticationService, not the one after the update in the other controllers.
it seems that my HeaderCtrl is defining all his scope objects only one time and then there's no more use for the Ctrl, but I still want his HTML page to be updated after the update in object values in other controllers.
to sum it up: the value of the object I want is stored in one of my services, and I am unable to display it in my HTML page because I can't seem bind it correctly.
You can send messages between the controllers using a service. The service looks something like this...
aModule.factory('messageService', function ($rootScope) {
var sharedService = {};
sharedService.message = {};
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function () {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
In the controller that is sending the message, inject this service...
aModule.controller("sendingController", function ($scope, messageService) {
Then add a method that will broadcast the change to any controller that is listening...
$scope.sendMessage = function (someObject) {
messageService.prepForBroadcast(someObject);
},
In any controller that wants to receive the message, inject the service, and add a handler like this to do something with the message...
$scope.$on('handleBroadcast', function() {
//update what you will..
$scope.something = messageService.message;
});

Bind the value of a service to a controller so it updates if the service updates

There are a lot of references that discuss this, but I just need someone to confirm if this is right or not. If i have a service which I want to share information with a controller, and the controller should update on changes to the service I need to return an object from the service, like:
.factory('myService', ['$http', function($http) {
var data = {};
var service = {
constant: 1234,
getData: function() {
return data;
},
doCalculation: function() {
service.constant = data.const*25;
},
requestData: function() {
return $http.get('/blah')
.then(function( response ) {
data = response.data;
}
}
}
return service;
}])
Now to pass it to a controller for use and have it update if requestData is invoked again during maybe a route resolve() I would and can't do:
.controller('myCtrl', ['myService', function(myService) {
var self = this;
// PART 1
self.data = myService.constant; // this is not bound and will not update, yes?
self.data1 = myService.getData(); // this is not bound and will not update, yes?
// So, the above would be assigned or invoked only once on init of controller and
// would have to reset manually by assigning either a value or the result of the
// the function call again
self.myService = myService; // pass entire service
// Now, in controller functions or in the UI I can invoke the functions or access
// values, and those results will be bound and update on changes to the service
// since I've passed it in its entirety
self.getData = function() {
return self.myService.getData();
}
// PART 2
self.getData = myService.getData; // would you ever do this?
// You wouldn't have to pass the entire service if it had a bunch of different
// parts that maybe you didn't want to be available...
}]);
PART 1
<div ng-show="myCtrl.myService.doCalculation() === someNumber">You can't see me unless doCalculation resolves to someNumber</div>
PART 2
<div ng-show="myCtrl.getData() === anotherNumber">Would this work? and would you want to do this?</div>
I just can't seem to get the concept of how sharing data between a service(s) and a controller(s) works, and when it is working and when it won't. If all you can do is say correct, wrong, wrong, oh man so wrong, that's kewl, but if you can also say and this is why, I'd be ecstatic to put this away as finally resolved so I don't keep questioning it.
I wouldn't go too far here..
A controller is your view's helper. You need to generate vars and functions on your scope to help your view accomplish things.
Your business model however, is something that you would like to have one reference.
What I do is create my business model on a service, so multiple entities can share it(e.g. other services, directives, controllers etc.).
When my controller kicks in, I add a pointer to the model from the service and use the same reference between them. I bind the models properties to the view.
So:
A controller has it's own methods(dont share the service's methods). A controllers method should be short and use a service method as a helper.
A controller should have a reference to the business model which is created by a service. All your ajax calls should come from the service and populate\send the model that the service is holding.
A controller should have basic view functions(e.g. decide which css class to apply to an element). When you submit a form, the controller function should call the service's submit which will perform you ajax call.
Example:
Html:
<div ng-app="app">
<div ng-controller="myCtrl">
<input type="text" ng-model="Model.propA" />
<br/>
<input type="text" ng-model="Model.propB" />
<div ng-show="ShouldShowSecondDiv()">Second Div.</div>
<br/>
<button ng-click="SubmitForm()">Submit</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('myCtrl', function ($scope, myService) {
// simple controller "view method".
$scope.ShouldShowSecondDiv = function () {
return $scope.Model.propB > 12;
};
// "complex" "non-view method" -> use service.
$scope.SubmitForm = function () {
myService.SubmitModelToServer();
};
// get the ref. from the service.
$scope.Model = myService.GetModel();
});
app.service('myService', function () {
this.Model = {};
// perform an ajax to get the model or whatever.
this.GetModel = function () {
this.Model = {
propA: 'Im prop A',
propB: 12
};
return this.Model;
};
// submit it to the server via $http. Check the log and you will see the binding(if you changed a value in the view).
this.SubmitModelToServer = function () {
console.log("ajax or whatever....submitted");
console.log(this.Model);
};
});
JSFIDDLE.

Why the modifications i apply on a service don't impact the DOM on all the controllers it is involved

I am new using AngularJS, i am interesting about the fact that when we update a data, Angular automatically impacts the modifications everywhere the data is involving.
But unfortunately, i can't make it works.
The simple thing i am trying to do is to make a change on a controller B, and i want the changes to be achieve on the controller A, since the data is referering to the same Service.
The data is correctly impacting on the both controllers, but the DOM is not updating according to this modification, here is the test:
HTML
<body>
<div ng-controller="ACrtl">
<h1>{{is_logged}}</h1> <!-- Always false -->
<button ng-click="check()">Check</button> <!-- true/false -->
</div>
<div ng-controller="BCrtl">
<button ng-click="{{is_logged=!is_logged}}">Toggle throught the DOM</button> <!-- Doesn't change anything on the Javascript -->
<button ng-click="toggle()">Toggle throught the controller</button> <!-- Change the Javascript but doesn't impact the other controller's scope -->
</div>
</body>
JS
var app = angular.module('MyApp', []);
app.controller('ACrtl', function($scope, UserService) {
$scope.is_logged = UserService.is_logged;
$scope.check = function() {
console.log('is_logged='+UserService.is_logged); //The change is correctly made when changin is_logged on the controller B.
$scope.is_logged = UserService.is_logged;
};
});
app.controller('BCrtl', function($scope, UserService) {
$scope.is_logged = UserService.is_logged;
$scope.toggle = function() {
UserService.is_logged = !UserService.is_logged;
};
});
app.factory('UserService', function() {
var User = {
is_logged: false
};
return User;
});
I hope AngularJS is able to do this and it's something i am doing wrong in my code !
Here is a plunker
Primitive variables (like boolean) are passed by value in Javascript, and the variables $scope.is_logged are just copies of their values in the service. So, if the original service value is changed, then this won't affect any copies on the scopes.
A standard way or re-factoring this would be to share an object between the controllers, and not a primitive, so
app.factory('UserService', function() {
return {
status: {
is_logged: false
}
};
});
And then used in the controllers
$scope.status = UserService.status;
So the controller can change $scope.status.is_logged, and the changes will be seen in all the controllers.
You can see this at:
http://plnkr.co/edit/GLZmdsAnn3T5Xw80h4sV?p=preview
When you assign is_logged to the scope on each controller you are creating a new property on each controller, both of which are initialised to the value from UserService.
In your case what you can do is expose the service on the scope of each controller like so:
$scope.data = UserService
and in your view:
<h1>{{data.is_logged}}</h1>
Have a look at this answer and the links that it mentions.

Resources