Can't bind a value on a setInterval operation - javascript

I'm trying to create a simple example with 2 controllers and 2 services, where one of the services is called every 3 seconds.
A controller1 calls a service, which changes a value used in a controller2. I pass the value throught another service, but the value is not updated on my html page, only after I press 'Stop' button of my example (in bellow).
Can I update page value on every poll?
<html ng-app="myApp">
<head>
<title>MyApp</title>
<script src="angular.js"></script>
<script>
var app = angular.module('myApp',[]);
app.service('SimpleService', function() {
var _value = 0;
var setValue = function(v) {
_value = v;
}
var getValue = function(){
return _value;
}
return {
setValue: setValue,
getValue: getValue
};
})
app.service('PollService', function(SimpleService) {
var poll = undefined;
var startPolling = function(){
poll = setInterval(function(){
console.info('poll...');
console.info(SimpleService.getValue());
SimpleService.setValue(SimpleService.getValue()+1);
}, 3000);
}
var stopPolling = function(){
clearInterval(poll);
}
return {
startPolling: startPolling,
stopPolling: stopPolling
};
})
app.controller('Controller1',function($scope, PollService){
var poll = undefined;
$scope.startPolling = function(){
PollService.startPolling();
}
$scope.stopPolling = function(){
console.info('stop');
PollService.stopPolling();
}
});
app.controller('Controller2', function($scope, SimpleService){
$scope.newVal = function(){
return SimpleService.getValue();
}
});
</script>
</head>
<body>
<div ng-controller="Controller1">
<button ng-click="startPolling()">Start</button>
<button ng-click="stopPolling()">Stop</button>
<br/>
<br/>
</div>
<div ng-controller="Controller2">
<h5>New value: {{newVal()}}</h5>
</div>
</body>
Regards.

You need to invoke the digest cycle each time you're invoking the setter in the SimpleService. The view bindings will not update unless a fresh digest cycle starts. Here is the working code snippet.
app.service('SimpleService', function($rootScope) {
var _value = 0;
var setValue = function(v) {
_value = v;
$rootScope.$apply(); //added this here
}
var getValue = function(){
return _value;
}
return {
setValue: setValue,
getValue: getValue
};
})
You need to do scope.apply(). $apply evaluates any expressions in your template and starts a new digest cycle.In the $digest phase the scope examines all of the $watch expressions and compares them with the previous value. You can checkout the documentation about it here.
Some other issues I noticed in your code:
app.controller('Controller2', function($scope, SimpleService2){
$scope.newVal = function(){
return SimpleService.getValue();
}
});
Here, correct the name to SimpleService.
There is no method for stopPropagation.I added it as follows in the PollService to get your code working:
var stopPolling = function(){
clearInterval(poll);
}

Since setInterval is not an angular function, angular doesn't know that the value is updated and can't tell the DOM that the value is dirty, and therefore is not updated.
If you want to continue using setInterval instead of angular's $interval, you can inject $rootScope into your polling service, and then you can use $rootScope.$apply to digest the value which will update the DOM. You can do
$rootScope.$apply(function(){
SimpleService.setValue(SimpleService.getValue()+1);
});
inside your setInterval function.
Otherwise, $interval is an angular.js wrapper for the setInterval function, which does not require manually calling $apply.
I made changes to your code and some examples for how you might be able to better structure your use of service in controller scope.
See here for updated code
Mainly, since a service is an object, we can directly reference the service's context in the DOM by adding the service itself to the controller scope, rather than reference's to the individual object keys. This eliminates the need to call a getter function from the DOM (as you do when you call {{newValue()}} ).

Related

How to keep service data the same on multiple controllers without using $watch?

There's an older answer I saw here on S.O, which states:
"This JavaScript works as we are passing an object back from the
service rather than a value. When a JavaScript object is returned from
a service, Angular adds watches to all of its properties."
It then gives this example:
JavaScript:
angular.module("Demo", [])
.factory("DemoService", function($timeout) {
function DemoService() {
var self = this;
self.name = "Demo Service";
self.count = 0;
self.counter = function(){
self.count++;
$timeout(self.counter, 1000);
}
self.addOneHundred = function(){
self.count+=100;
}
self.counter();
}
return new DemoService();
})
.controller("DemoController", function($scope, DemoService) {
$scope.service = DemoService;
$scope.minusOneHundred = function() {
DemoService.count -= 100;
}
});
HTML
<div ng-app="Demo" ng-controller="DemoController">
<div>
<h4>{{service.name}}</h4>
<p>Count: {{service.count}}</p>
</div>
</div>
I asked the OP if $watch was necessary to keep the data the same across controllers, and they said "no" without elaborating.
But, when I test it out, the DemoService "count" value is not the same on both controllers, unless I use $watch.
Here's a fiddle with the example below, but with an added controller:
http://jsfiddle.net/qqejytbz/1/
Given this example, how it is possible to keep the value the same, but without the use of $watch or $broadcast?
Updated fiddle thanks to selected answer:
http://jsfiddle.net/qqejytbz/4/
In your second controller, you're storing the value of count directly, which makes a copy of count at the time it was assigned. If count were an object, then {{service.count}} and $scope.count would just be a reference to the same object. Then modified properties of the object would be synchronized between controllers.
Eg.
//in DemoService
self.count = {value: 0};
// Binding in html for controller 1
{{service.count.value}}
// Assignment in controller 2:
$scope.count = DemoService.count;
//$scope.count.value === controller1.service.count.value === 0
service.count.value += 100
//$scope.count.value === controller1.service.count.value === 100
Note that Angular may not pick up on the changes to the object until the next full digest cycle.

Controller change data of another controller on click

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.

Angular binding to service value not updating

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.

How do I update my Angular $scope variable based off of a global variable in another non Angular JS file

How do I update my Angular $scope variable based off of a global variable in another non JS Angular file? I'm using a jQuery plug-in to generate some value. I want to connect this value to my $scope variable in my controller. However, this value is constantly changing, and I don't think my $scope variable auto-updates with changes to a non Angular variable. What's a good way to do this? Thanks!
You can use a $watch on the global variable by wrapping it in a function. Inside the controller's link function, add this:
$scope.$watch(
function() {
return globalVar;
},
function(newValue, oldValue) {
// Global var changed! Do stuff.
$scope.scopeVar = newValue;
});
AngularJS keeps polling or checking for the return value of the first function, which returns your global variable. This is done multiple times per digest cycle.
When the value changes, it calls the second function, which is the event handler for the change. You can add logic for handling the change here. The change handler is passed in both the old and the new values for convenience.
In this case, I am simply setting a scope variable with the changed global value.
If the global variable is updated outside of angujarjs, one possible solution is what is proposed by #metacubed but it has a problem that is if there is no digest cycles executed by other operations the variables will not get updated... http://jsfiddle.net/arunpjohny/Lfz8bo11/2/
So another solution I can propose is to use a interval based approach(It can be costly because of the constant executions of digest cycles) like
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope, $interval) {
$scope.mycounter = counter;
$interval(function() {
$scope.mycounter = counter;
}, 1000);
$scope.something = function() {}
})
var counter = 0;
setInterval(function() {
counter++;
}, 500);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="my-app" ng-controller="AppController">
{{mycounter}}
<button ng-click="something()">Do something</button>
</div>
The interval solution seems to work, but as #Arun-p-johny said, could have a lot of perfomance cost,
I suggest another aproach, working around: 'calling the digist cycle of the scope only when the variable changes'
jsfiddle
var updateDigest = function(){
var appElement = document.querySelector('[ng-app=my-app]');
var appScope = angular.element(appElement).scope();
var controllerScope = appScope.$$childHead;
controllerScope.$digest();
};
var counter = 0;
setInterval(function () {
counter++;
updateDigest();
}, 500);
See this answer
This is working supposing the first child is the scope you are using.. ( in your app could not be the case ), so maybe a work around as getting the rootscope( eg. jsfiddle ) doing an emit or setting a variable in rootscope.
Or a better and elegant solution that I would recommend.. bring the external library to angular context ( create a wrapper ), seems for me the better solution.
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope, $rootScope) {
$scope.mycounter = counter;
$rootScope.$on('updateOuter', function(e, d) {
console.log('d', d);
$scope.mycounter = d;
$scope.$apply();
});
$scope.something = function() {}
});
var updateDigest = function(param) {
var appElement = document.querySelector('[ng-app=my-app]');
var appScope = angular.element(appElement).scope();
var rootScope = appScope;
rootScope.$emit('updateOuter', param);
};
var counter = 0;
setInterval(function() {
counter++;
updateDigest(counter);
}, 500);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>
<body ng-app="my-app">
<div id="container" ng-controller="AppController">
{{mycounter}}
</div>
</body>

AngularJS automatically updating controller data by service

I'm having some basic problems with angular at the moment. I just wrote a service that reads the temperature of an external device in an interval of five seconds. The service saves the new temperature into a variable and exposes it via a return statement. This looks kind of this (simplified code):
angular.service("tempService", ["$interval", function ($interval) {
//revealing module pattern
var m_temp = 0,
requestTemp = function() {//some logic here},
onResponseTemp = function (temp) {
m_temp = temp;
},
//some other private functions and vars ...
foo = bar;
//request new temperture every 5s, calls onResponseTemp after new data got received
$interval(requestTemp, 5000);
return {
getTemp = function(){return m_temp;}
}
}]);
I use a controller to fetch the data from the service like this:
angular.controller("tempCtrl", ["$scope", "tempService", function ($scope, tempService) {
$scope.temp = tempService.getTemp();
}]);
In my view I access it like this:
<div ng-controller="tempCtrl">
<p>{{temp}}</p>
</div>
But I only get 0 and the value never changes. I have tried to implement a custom Pub/Sub pattern so that on a new temperature my service fires an event that my controller is waiting for to update the temperature on the scope. This approach works just fine but I'm not sure if this is the way to go as angular brings data-binding and I thought something this easy had to work by itself ;)
Help is really appreciated.
Please see here http://jsbin.com/wesucefofuyo/1/edit
var app = angular.module('app',[]);
app.service("tempService", ["$interval", function ($interval) {
//revealing module pattern
var m_temp = {
temp:0,
time:null
};
var requestTemp = function() {
m_temp.temp++;
m_temp.time = new Date();
};
var startTemp = function() {
$interval(requestTemp, 3000);
};
return {
startTemp :startTemp,
m_temp:m_temp
};
}]);
app.controller('fCtrl', function($scope,tempService){
$scope.temp = tempService;
$scope.temp.startTemp();
});
You are returning a primitive from your service, if you want to update an primative you need to reftech it. You should return an object, as on object is passed by reference, you get the actual values in your controller.
do this in your service:
return m_temp;
And this in your controller:
$scope.temp = tempService;
and your view will update as soon as the service gets updated.
Does this help you?
i think you should use $interval in controller ot in service
$interval(tempService.getTemp(), 5000);

Categories