I want to pass some data from one controller to a second one via an onClick-Event. I tried to use a service between the two controllers but it seems that the controller who receives the data from the service doesn't recognize the onClick-Event of the first controller which leads to static/non changing data.
OnClick function (Controller 1)
$scope.select = function(index){
vm.currentActive = index;
sessionService.setState(index);
};
Exchange service
app.service('sessionService', function() {
var state = null;
var setState = function(changestate){
state = changestate;
};
var getState = function(){
return state;
};
return {
setState: function(changestate){
setState(changestate);
},
getState: function(){
return state;
}
};
});
Receiving Controller (Controller 2)
app.controller('ContentController', function ($scope, sessionService)
{
var vm = this;
vm.currentActive = sessionService.getState();
});
In the end I want that the state of Controller 2 changes whenever the OnClick-Event is triggered in controller 1. Is this way with the service the best or what do recommend to change the data in controller 2 after a click ?
One option for watching the state of a service is to use $scope.$watch with a function that returns the value to be watched for changes.
$scope.$watch(function(){ return sessionService.getState(); }, function(newValue, oldValue){
//Do something
});
If the value in the service is changed, the watch will pick up the change on the next digest cycle. With this method there's no need to have your service or other controller try and signal that the value has changed.
If your service's getter method does not depend on this, you can simplify the watcher by just passing the getter method as the watch function rather than using a wrapper function.
$scope.$watch(sessionService.getState, function(newValue, oldValue){
//Do something
});
You can add onChange event to service:
app.service('sessionService', function() {
var state = null;
var callbacks = [];
var setState = function(changestate) {
callbacks.forEach(function(callback) {
callback(state, changestate);
});
state = changestate;
};
var getState = function() {
return state;
};
return {
setState: function(changestate) {
setState(changestate);
},
getState: function() {
return state;
},
onChange: function(fn) {
if (typeof fn == 'function') {
callbacks.push(fn);
}
}
};
});
The reason your Receiving Controller is not getting the updated value is because the state property is copied into vm.state at the point of the directive definition object's initialization.
vm.currentActive = sessionService.getState();
Here, getState is only called once, so it won't matter if that state value is later updated...
One Solution
One option would be to call getState from the controller's view (which will get re-called (i.e. the value will be updated) with every digest cycle)...note this strategy has performance implications...
Another Solution
Another option is to leverage the trickle down effect of referenced objects (or as Miško explains in this Angular Best Practices video, "...if you don't have a dot, you're doing it wrong..."
You could utilize this strategy by using an object to store the state in your Exchange Service...
app.service('sessionService', function() {
var data = {};
var setState = function(changestate){
data.state = changestate;
};
var getState = function(){
return data.state;
};
return {
setState: setState,
data: data
};
});
Receiving Controller
app.controller('ContentController', function ($scope, sessionService) {
var vm = this;
vm.data = sessionService.data;
});
Then whenever data.state is updated in sessionService, vm.data.state will (by virtue of referenced data) contain the updated data as well.
In other words, vm.data.state will always contain the most up to date value of sessionService.data.state because they both refer to the same object.
Related
I'm new to this..
I have a controller
.controller('AudioCtrl', function($scope, Audio, AudioSockets) {
$scope.audioSockets = AudioSockets.list;
$scope.$watch(function() {return AudioSockets.list}, function () {
$scope.audioSockets = AudioSockets.list;
console.log(AudioSockets.list);
});
That references a service
.service('AudioSockets', function() {
// public API
this.list = sockets;
});
Where I'm trying to use a Javascript array that get's updated via external code
var sockets = [];
How do I get the value of what's in those sockets into my Controller?
Add third parameter boolean true to make deep watch
$scope.$watch(function(){
return AudioSockets.list
},
function(oldVal,newVal){
console.log(newVal);
},true);
I have here a service sharedData and a controller HostController. I'm looking to have the HostController watch the saveDataObj variable in the sharedData service, and then assign that value to host.dataOut, which is then reflected in the view as an ngModel attribute.
My issue is that I don't really know how to use $watch. I want host.dataOut to be updated every time saveDataObj in the sharedData service is changed (or at least, each time setData is called).
The following code produces a line in the console:
sharedData.getData(): Object { }
This is to be expected during initialisation. However, when I call setData via another controller to change saveDataObj to a different value, nothing is logged to the console.
What am I doing wrong?
angular.module('app', [])
.factory('sharedData',function(){
var saveDataObj = {};
return {
getData: function(){
return saveDataObj;
},
setData: function(data){
saveDataObj = data;
}
};
})
.controller('HostController',['$scope','sharedData',function($scope,sharedData){
$scope.sharedData = sharedData;
var host = this;
host.dataOut = {};
$scope.$watch(
'sharedData.getData()',
function handleDataChange(newValue,oldValue) {
console.log("sharedData.getData():",newValue);
host.dataOut = newValue;
}
);
}])
.controller('HostController',['$scope','sharedData',function($scope,sharedData){
$scope.sharedData = sharedData;
var host = this;
host.dataOut = {};
$scope.serviceData = sharedData.getData(); //local variable
$scope.$watch('serviceData', function(newValue, oldValue) {
console.log("sharedData.getData():",newValue);
host.dataOut = newValue;
}
);
}]);
Hope help you!
Answer from me, the OP:
The issue was not that I was trying to $watch something in a service - it was that I was trying to $watch an Object. For this, I needed to declare the third term of $watch, objectEquality, to be true.
$scope.$watch(
'sharedData.getData()',
function handleDataChange(newValue,oldValue) {
console.log( "sharedData.getData():", newValue );
},
true
);
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
I'm going to mark this as best answer because I'm a narcissist.
I want to be able to share data between two controllers so that I can send a boolean to the service from the first controller which is turn triggers a change in the second controller.
Here is what the service looks like
exports.service = function(){
// sets Accordion variable to false ;
var property = true;
return {
getProperty: function () {
return property;
},
setProperty: function(value) {
property = value;
}
};
};
Now the first controller
exports.controller = function($scope, CarDetailsService, AccordionService ) {
$scope.saveDetails = function() {
AccordionService.setProperty(false);
}
}
and the second one
exports.controller = function($scope, AccordionService ) {
$scope.isCollapsed = AccordionService.getProperty();
}
The use case is that when i click on a button on the first controller,the service updates the data inside it, which is then served on the second controller, thus triggering a change in the second controller.
I have been looking around for quite some time but couldn't find a solution to this. Maybe im just stupid.
On the second controller you can $watch the variable you change in the first:
scope.$watch('variable', function(newValue, oldValue) {
//React to the change
});
Alternatively, you can use the $broadcast on the rootScope:
On the first controller:
$rootScope.$broadcast("NEW_EVENT", data);
On the other controller:
scope.$on("NEW_EVENT", function(event, data){
//use the data
});
I cannot get a binded service value to update when it is changed. I have tried numerous methods of doing so but none of them have worked, what am I doing wrong? From everything I have seen, this seems like it should work...
HTML:
<div class="drawer" ng-controller="DrawerController">
{{activeCountry}}
</div>
Controller:
angular.module('worldboxApp')
.controller('DrawerController', ['$scope', 'mapService', function($scope, mapService) {
$scope.$watch(function() { return mapService.activeCountry }, function(newValue, oldValue) {
$scope.activeCountry = mapService.activeCountry;
});
}]);
Service:
angular.module('worldboxApp').
service('mapService', function(dbService, mapboxService, userService) {
this.init = function() {
this.activeCountry = {};
}
this.countryClick = function(e) {
this.activeCountry = e.layer.feature;
};
this.init();
});
I put a break point to make sure the mapService.activeCountry variable is being changed, but all that ever shows in the html is {}.
If you work with objects and their properties on your scope, rather than directly with strings/numbers/booleans, you're more likely to maintain references to the correct scope.
I believe the guideline is that you generally want to have a '.' (dot) in your bindings (esp for ngModel) - that is, {{data.something}} is generally better than just {{something}}. If you update a property on an object, the reference to the parent object is maintained and the updated property can be seen by Angular.
This generally doesn't matter for props you're setting and modifying only in the controller, but for values returned from a service (and that may be shared by multiple consumers of the service), I find it helps to work with an object.
See (these focus on relevance to ngModel binding):
https://github.com/angular/angular.js/wiki/Understanding-Scopes
If you are not using a .(dot) in your AngularJS models you are doing it wrong?
angular.module('worldboxApp', []);
/* Controller */
angular.module('worldboxApp')
.controller('DrawerController', ['$scope', 'mapService',
function($scope, mapService) {
//map to an object (by ref) rather than just a string (by val), otherwise it's easy to lose reference
$scope.data = mapService.data;
$scope.setCountry = setCountry; //see below
function setCountry(country) {
// could have just set $scope.setCountry = mapService.setCountry;
// however we can wrap it here if we want to do something less generic
// like getting data out of an event object, before passing it on to
// the service.
mapService.setCountry(country);
}
}
]);
/* Service */
angular.module('worldboxApp')
.service('mapService', ['$log',
function($log) {
var self = this; //so that the functions can reference .data; 'this' within the functions would not reach the correct scope
self.data = {
activeCountry: null
}; //we use an object since it can be returned by reference, and changing activeCountry's value will not break the link between it here and the controller using it
_init();
function _init() {
self.data.activeCountry = '';
$log.log('Init was called!');
}
this.setCountry = function _setCountry(country) {
$log.log('setCountry was called: ' + country);
self.data.activeCountry = country;
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="worldboxApp">
<div ng-controller="DrawerController">
<button ng-click="setCountry('USA')">USA</button>
<br />
<button ng-click="setCountry('AUS')">AUS</button>
<br />Active Country: {{data.activeCountry}}
</div>
</div>
In some case $watch is not working with factory object. Than you may use events for updates.
app.factory('userService',['$rootScope',function($rootScope){
var user = {};
return {
getFirstname : function () {
return user.firstname;
},
setFirstname : function (firstname) {
user.firstname = firstname;
$rootScope.$broadcast("updates");
}
}
}]);
app.controller('MainCtrl',['userService','$scope','$rootScope', function(userService,$scope,$rootScope) {
userService.setFirstname("bharat");
$scope.name = userService.getFirstname();
$rootScope.$on("updates",function(){
$scope.name = userService.getFirstname();
});
}]);
app.controller('one',['userService','$scope', function(userService,$scope) {
$scope.updateName=function(){
userService.setFirstname($scope.firstname);
}
}]);
Here is the plunker
Note:- In Some case if broadcast event is not fired instantly you may use $timeout. I have added this in plunker and time depends on your needs. this will work for both factories and services.
I'm new to Angular and I'm trying to create an app that pulls data from a dynamically generated JSON file every 5 minutes and updates the view with the new data. The JSON file contains all the data for the website, which is data for a slider and for a list of events. I've read that global data can be stored in the $rootScope, or retrieved and served using a .service or .factory. I've tried different ways with no success and I'm lost. What is the best way to periodically pull data from an api for the entire app, store it, and use it?
Currently I have this code:
app.factory("Poller", function Poller($http, $timeout){
var data = {'slider' : [], 'activities' : []};
var getData = function(){
$http.get('http://example.com/json.php')
.then(function(r){
data = r.data;
});
$timeout(getData, 1000*60*5);
}
getData();
return{
data: data
}
});
app.controller('ListController', function(Poller){
this.activities = Poller.data.activities;
});
I think you're looking for something like this:
See plnkr
app.factory("Poller", function Poller($http, $interval){
var data = {
'newData': {
'one' : '',
'key' : ''
},
'fetchCount': 0
};
var getData = function() {
data.fetchCount += 1;
$http.get('http://echo.jsontest.com/key/value/one/two')
.then(function(response){
data.newData = response.data;
});
};
var loopGetData = function() {
$interval(getData, 5000);
};
loopGetData();
return{
data: data,
};
});
app.controller('ListController', function($scope, Poller){
$scope.activities = {'one' : '', 'key' : ''};
$scope.Initialize = function () {
$scope.$watch(function () { return Poller.data.newData; },
function(newValue, oldValue) {
if (newValue !== oldValue) {
$scope.activities = newValue;
}
});
};
$scope.Initialize();
});
app.controller('CountController', function($scope, Poller){
$scope.fetchCount = 0;
$scope.Initialize = function () {
$scope.$watch(function () { return Poller.data.fetchCount; },
function(newValue, oldValue) {
if (newValue !== oldValue) {
$scope.fetchCount = newValue;
}
});
};
$scope.Initialize();
});
So the problem here looks like you have defined the function getData() inside the Poller factory that makes the ajax request, but didn't bind that to a key inside the factory's returning object.
Try adding this,
return{
data: data,
getData: getData
}
And then remove the getData() you have right before the returning block.
Now I feel like depending on your app's setup, if you want to achieve long polling effects, you should attach getData() to a variable in some top-level controller that is always present so that you can invoke the getData() periodically using $timeout.
Speaking of $timeout is that you should extract the $timeout outside of the getData() and so that the function itself simply makes the ajax request. More modular, single responsibility design that way, which makes that factory function more usable in other areas of your app if you ever need.
If you want to update the information after you finish the ajax requests every five minutes, you should looking into $broadcast and $rootScope, you can basically tell your entire Angular app that some rootScope variable or service has changed, and they will update accordingly. You need some kind of callback set up after the ajax request inside whichever controller you are invoking the getData() function.