I was trying the code given in angularjs docs (given here: http://jsfiddle.net/zGqB8/)
It just implements a time factory and uses $timeout to update the time object after each second.
angular.module('timeApp', [])
.factory('time', function($timeout) {
var time = {};
(function tick () {
time.now = new Date().toString();
$timeout(tick, 1000); // how to do it using setInterval() ?
})();
return time;
});
How would I do it using setInterval() function instead of $timeout() ?
I know that one need to use scope.$apply() to enter the angular execution context but how would that work in a factory function? I mean, in a controller, we have a scope, but we don't have scope in a factory function?
You can use $timeout as an interval.
var myIntervalFunction = function() {
cancelRefresh = $timeout(function myFunction() {
// do something
cancelRefresh = $timeout(myIntervalFunction, 60000);
},60000);
};
If the view is destroyed, you can destroy it with listening on $destroy:
$scope.$on('$destroy', function(e) {
$timeout.cancel(cancelRefresh);
});
Update
Angular has implemented an $interval feature in version 1.2 - http://docs.angularjs.org/api/ng.$interval
Legacy example below, disregard unless you're using a version older than 1.2.
A setInterval implementation in Angular -
I've created a factory called timeFunctions, which exposes $setInterval and $clearInterval.
Note that any time I've needed to modify scope in a factory I've passed it in. I am unsure if this meets the "Angular way" of doing things, but it works well.
app.factory('timeFunctions', [
"$timeout",
function timeFunctions($timeout) {
var _intervals = {}, _intervalUID = 1;
return {
$setInterval: function(operation, interval, $scope) {
var _internalId = _intervalUID++;
_intervals[ _internalId ] = $timeout(function intervalOperation(){
operation( $scope || undefined );
_intervals[ _internalId ] = $timeout(intervalOperation, interval);
}, interval);
return _internalId;
},
$clearInterval: function(id) {
return $timeout.cancel( _intervals[ id ] );
}
}
}
]);
Example Usage:
app.controller('myController', [
'$scope', 'timeFunctions',
function myController($scope, timeFunctions) {
$scope.startFeature = function() {
// scrollTimeout will store the unique ID for the $setInterval instance
return $scope.scrollTimeout = timeFunctions.$setInterval(scroll, 5000, $scope);
// Function called on interval with scope available
function scroll($scope) {
console.log('scroll', $scope);
$scope.currentPage++;
}
},
$scope.stopFeature = function() {
return timeFunctions.$clearInterval( $scope.scrollTimeout );
}
}
]);
Could you call a normal JavaScript method and then within that method wrap the Angular code with an $apply?
Example
timer = setInterval('Repeater()', 50);
var Repeater = function () {
// Get Angular scope from a known DOM element
var scope = angular.element(document.getElementById(elem)).scope();
scope.$apply(function () {
scope.SomeOtherFunction();
});
};
Latest release candidate (1.2.0 rc3) has interval support. See changelog
Related
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()}} ).
I have a recursive method that, if a flag is set, will call itself every five seconds. I'm trying to write a test that spies on the method, calls it, waits six seconds and then expects the method to have been called twice. My test fails, as the spy reports the method only being called once (the initial call).
I'm using the Angular style guide, so am attaching these methods to a placeholder for this. I suspect there may be an issue with scoping of the controller returned from angular-mocks $controller(), but I'm not sure—most people are attaching methods to $scope.
Without attaching methods to $scope, how can I create a spy to verify that my method has been called twice?
app.js:
'use strict';
angular
.module('MyApp', [
//...
]);
angular
.module('MyApp')
.controller('MyController', MyController);
MyController.$inject = [
//...
];
function MyController() {
var vm = this;
vm.callMyself = callMyself;
vm.flag = false;
function callMyself () {
console.log('callMyself');
if (vm.flag) {
console.log('callMyself & flag');
setTimeout(vm.callMyself, 5000);
}
}
}
appSpec.js:
describe('MyController', function () {
var $scope;
beforeEach(function () {
module('MyApp');
});
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
controllerInstance = $controller('MyController', {$scope: $scope});
}));
it('should call itself within 6 seconds if flag is true', function (done) {
controllerInstance.flag = true;
spyOn(controllerInstance, 'callMyself');
controllerInstance.callMyself();
setTimeout(function () {
expect(controllerInstance.callMyself).toHaveBeenCalledTimes(2);
done();
}, 6000);
}, 7000);
});
Working Plunker
You need to use .and.callThrough() to further execute the function that would call itself:
By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation.
spyOn(controllerInstance, 'callMyself').and.callThrough();
Tested in the plunker - it works.
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>
I have an AngularJS service. This service uses a $timeout in its function. I'm trying to figure out how to test this service in various situations. Here is my code:
myApp.factory('myService', ['$timeout', function($timeout) {
var isRunning = false;
var myTimer = null;
return {
myTimer: myTimer,
isRunning: isRunning,
execute: function(time) {
this.isRunning = true;
this.myTimer = $timeout(
function() {
this.isRunning= false;
this.myTimer= null;
},
time
);
}
};
}]);
Now, I'm trying to test this code. I have the following written:
describe('myService', function () {
var myService = null;
var $timeout = null;
var $rootScope = null;
beforeEach(inject(function (_$timeout_, _$rootScope_, _myService_) {
myService = _myService_;
$timeout = _$timeout_;
$rootScope = _$rootScope_;
}));
it('should run for one second', function() {
myService.execute(1000);
$timeout.flush();
$rootScope.$digest();
expect(myService.isRunning).toBe(false);
});
});
The test "works". However, if 750 milliseonds have elapsed, I would expect myService.isRunning to be true. However, I do not know how to test for that scenario. Can someone please show me how to test tht situation? thank you
You can imitate a certain amount of time passing with the $timeout.flush method. The method takes an optional parameter called delay. It's explained in the documentation here. With that in mind, you could write a test that looks like this:
it('should run for one second', function() {
myService.execute(1000);
$timeout.flush(750);
$rootScope.$digest();
expect(myService.isRunning).toBe(true);
});
I am building a subscriber/observer pattern for displaying data in realtime for my angular app.
The observer is built with a factory injected into the angular controller and whose role is to fetch data and update it. The basic code structure can he found in this fiddle: http://jsfiddle.net/ctrager/67QR7/3/
var myApp = angular.module('myApp', [])
.factory('MyFactory', [function () {
var Collection = {};
Collection.isLoaded = 0;
Collection.data = [1, 2];
Collection.username = "corey and eric";
Collection.update = function () {
Collection.data.push(new Date())
}
Collection.replace = function () {
// If you do Collection.data = []
// here you are doing the same thing
// as the empty collection bug. I can't
// tell you EXACTLY why this confuses angular
// but I'm 99% sure it's the same phenomenon
Collection.data = [new Date()]
}
Collection.replace_fixed = function () {
// This works
Collection.data.length = 0
Collection.data.push(new Date())
}
return Collection;
}])
function MyCtrl($scope, MyFactory) {
$scope.name = 'Eric';
$scope.items = MyFactory.data;
$scope.replace = function(){
console.log("replace")
MyFactory.replace()
//$scope.items = MyFactor.data;
}
$scope.replace_fixed = function(){
console.log("replace_fixed")
MyFactory.replace_fixed()
//$scope.items = MyFactor.data;
}
$scope.update = function(){
console.log("update")
MyFactory.update()
}
}
The factory (MyFactory) contains a collection (Collection.data). Any push (/splice) to that collection is reflected in the scope, but if I replace the entire collection (Collection.replace()) the change is no longer reflected in $scope. Any idea why?
This works:
http://jsfiddle.net/67QR7/4/
changed the thing stored on scope to be the factory instead of data. then the html repeat to do items.data.
So it looks like this is because you replaced the reference inside collection, but that doesn't change where $scope.items was pointing to.
So you are creating a reference to MyFactory.data from $scope.items. Angular puts a $watch on $scope.items and looks for changes. When you call MyFactory.replace, you change MyFactory.data, but $scope.items remains the same. So as far as your watch is concerned, nothing has happened.
You can fix this by using replace_fixed, or watch for changes to MyFactory.data. http://jsfiddle.net/KtB93/
$scope.MyFactory = MyFactory;
$scope.$watch("MyFactory.data", function(newData) {
console.log('myFactory.data changed');
$scope.items = newData;
});
Or alternatively (probably better), you can use a function as the watch expression so you don't have to plop MyFactory on the scope (http://jsfiddle.net/XAW54/1/):
$scope.$watch(function() {
return MyFactory.data;
}, function(newData) {
$scope.items = newData;
});