How do you reset a $timeout, and disable a $watch()?
The key to both is assigning the result of the function to a variable.
To cleanup the timeout, just “.cancel()” it:
var customTimeout = $timeout(function () {
// arbitrary code
}, 55);
$timeout.cancel(customTimeout);
The same applies to “$interval()”.
To disable a watch, just call it.
var deregisterWatchFn = $rootScope.$watch(‘someGloballyAvailableProperty’, function (newVal) {
if (newVal) {
// we invoke that deregistration function, to disable the watch
deregisterWatchFn();
...
}
});
You already got most of the answer, but one other point that might be useful is that you should always clean up a watch when the relevant scope is destroyed, so the following pattern is quite useful:
$scope.$on('$destroy', $scope.$watch(function() { /* ... */ });
This will ensure the $watch is always cancelled automatically on destruction of the scope.
Of course if you want to destroy the watch manually you would have to also save the result so you could call it, but this pattern lets you avoid that most of the time.
You can clear a $timeout by using $timeout.cancel like so:
var myTimer = $timeout(function() { /* ... */ }, 5000);
$timeout.cancel(yourTimer);
You can clear a $watch() by the deregister function it returns:
var dereg = $scope.$watch(function() { /* ... */ });
dereg(); // watcher is now gone
Related
In a project I'm working on we've got a variable on the $rootScope called events. I can access this in my controllers using $rootScope.events after injecting it to my controller.
It can take some time before the service sets the events on the $rootScope variable. Now am I adding a new functionality that needs the ID from the first event of the variable. The problem is, it's getting called before $rootScope.events is set. I can't figure out how to call the method in my controller after the $rootscope.events is set. I have used $watch before, how ever, it doesn't seem to work on this variable. The code I tried:
$scope.$watch('$rootScope.events', function() {
if ($rootScope.events.length > 0) {
getDetails(); // function I want to call after $rootscope.events is set
$log.debug($rootScope.events); // debugging line
}
});
I added the $rootScope.events.length > 0 to avoid it's getting in a infinite loop. Not sure if this is necessary. Is there a solution for what I need to add this functionality? Something like this watch? Or have I done something wrong?
I don't think you need more code then I've added to this post as I just inject $scope and $rootScope in my controller, and then $log.debug() should get called with the set variable. Currently it returns an empty variable. If I'm wrong just let me know in the comments.
It's been awhile, but I think you want this:
$rootScope.$watch('events', function() {
if ($rootScope.events.length > 0) {
getDetails(); // function I want to call after $rootscope.events is set
$log.debug($rootScope.events); // debugging line
}
});
events is a value on $rootscope but $rootscope.events is not a value on $scope.
To avoid cluttering $rootscope with watches, however, you should probably use:
$scope.$watch('$root.events', function() {
var events = $scope.$root.events;
if (events.length > 0) {
getDetails(); // function I want to call after events is set
$log.debug(events); // debugging line
}
});
Or simply:
$scope.$watch('$root.events', function(newValue, oldValue) {
if (newValue.length > 0) {
getDetails(); // function I want to call after events is set
$log.debug(newValue); // debugging line
}
});
RootScope documentation
Watch is on a string (scope variable) or a function.
$scope.$watch(function() {
return $rootScope.events;
}, function() {
console.log($rootScope.events);
}, true);
Instead of putting a watch for something that happen once, you can use a promise which your events service will resolve when the events will be ready.
Event Service : ($rootScope & $q injected)
// in constructor :
this.deferred = $q.defer();
$rootScope.eventPromise = deferred.promise;
// in a setup fonction or even within the constructor
setupEvent : function(){
.. doing some stuff ..
.. somewhere in a asynchronous call back :
$rootScope.events =... //setup events
this.deferred.resolve();// or me.deferred using var me=this if some closure trouble
.. somewhere else if it fails ..
this.deferred.reject();
}
Now let's be sure this will run before any controller will be loaded :
angular.run(['EventService', function(EventService){
// if you do everything in the constructor let the angular.run and don't run any code,
// this will make sure your events will start loading before angular will resolve the current routes.
EventService.setupEvent();
}]);
Now let's use it :
$rootScope.eventPromise.then(function(){
$rootScope.events // we're safe here.
});
I tried to follow along with this example but my code never enters the callback with the newValue when the input changes for some reason. The only difference in my example and the example bin below is that I'm using a service to hold the value instead of a controller variable.
I've been trying to make it work but I can't wrap my head around it. What am I missing?
http://jsbin.com/yinadoce/1/edit?html,js,output
Note that I've excluded the input and the steps prior to the value being updated in the service as this works perfect. The issue is only that my watch doesn't understand when the value has changed in the service.
EDIT: Forgot to inject $scope in the controller when I pasted over the code, now it's complete.
Controller:
coForms.controller('CoFormsCtrl', ['$scope', 'coFormsInfo', function($scope, coFormsInfo) {
$scope.$watch(angular.bind(coFormsInfo.getInfo(), function() {
return coFormsInfo.getInfo();
}), function(newVal) {
console.log(newVal);
});
}]);
Service:
coForms.service('coFormsInfo', [function() {
var info = {
filteredList: []
}
this.setFilteredList = function(list) {
info.filteredList = list;
};
this.getInfo = function() {
return info;
};
}]);
The watcher is there to detect any changes in the variable you're watching. How can he watch something that is not... Strictly present like a return value?
I'm not sure about what I'm saying because I'm new to angular, but the logic seems false there. You need to watch something declared to detect some changes.
You should call your service to get your infos when you need them and watch for an info variable.
EDIT
My bad there is something like that but you should declare it in a function maybe like the example on the documentation
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This function returns the value being watched. It is called for each turn of the $digest loop
function() { return food; },
// This is the change listener, called when the value returned from the above function changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
I have ng-model on a date variable. On changing that variable, I want to make some validations and during those validations I might change the start variable itself, and to make sure I don't get infinite-watch trigger, I'm de-registering the watch before, and re-registering it when the validation finishes.
The re-registering doesn't work. What am I doing wrong?
var watchStartTime = $scope.$watch('timeSelection.startTime', function(newValue, oldValue) {
if (newValue === oldValue) return;
validateStartEndTime();
}, true);
function validateStartEndTime() {
// De-register watch
watchStartTime();
// Do some stuff that might change timeSelection.startTime
// Re-register watch
watchStartTime();
}
I also tried setTimeout with 0 on the re-registering, thought it might work, but it doesn't.
the timeSelection.startTime changes (I know for a fact it does) but the function in $watch doesn't get called again.
What am I missing?
As stated by #Nikos Paraskevopoulos $watch returns the deregistration fn.
I think you should do something like the following...
var register = function() {
var unregister = $scope.$watch('timeSelection.startTime', function(newVal, oldVal) {
if (newVal === oldVal) {
return;
}
unregister();
validateStartEndTime();
register();
}, true);
};
register();
I'm reading through documentation on scope's method $watch here. The method accepts:
$watch(watchExpression, [listener];
Then they provide examples:
// let's assume that scope was dependency injected as the $rootScope
var scope = $rootScope;
scope.name = 'misko';
scope.counter = 0;
expect(scope.counter).toEqual(0);
scope.$watch('name', function(newValue, oldValue) {
scope.counter = scope.counter + 1;
});
expect(scope.counter).toEqual(0);
scope.$digest();
// the listener is always called during the first $digest loop after it was registered
expect(scope.counter).toEqual(1);
scope.$digest();
// but now it will not be called unless the value changes
expect(scope.counter).toEqual(1);
scope.name = 'adam';
scope.$digest();
expect(scope.counter).toEqual(2);
// Using a listener function
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This is the listener function --------- WHY ?????????????
function() { return food; },
// This is the change handler ---- THIS SHOULD BE A LISTNER FUNCTION
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
// No digest has been run so the counter will be zero
expect(scope.foodCounter).toEqual(0);
// Run the digest but since food has not changed count will still be zero
scope.$digest();
expect(scope.foodCounter).toEqual(0);
// Update food and run digest. Now the counter will increment
food = 'cheeseburger';
scope.$digest();
expect(scope.foodCounter).toEqual(1);
What I don't understand is that why they refer to function() { return food; } in the second example as a // This is the listener function if this is the function that should return the value that will be watched. which is a watchExpression?
The comment is a little bit misleading and you might file an issue or pull request to improve it.
As you said correctly there are 2 arguments for $watch(watchExpression, [listener])
watchExpression
The watchExpression is called on every call to $digest() and should return the value that will be watched.
The watch expression can be either a string or a function.
If you specify a function this is not the listener but rather a comparison function which will be called many many times so don't do any fancy stuff here ;)
This comparison function is used for the angular dirty checking.
More details on this can be found in this part of the video by the creator of angularJs: https://www.youtube.com/watch?feature=player_detailpage&v=ZhfUv0spHCY#t=1988
listener
This function is called whenever the value of your watchExpression changes.
So this a perfect place to update your models and do your fancy business logic
I have a ExtJS 4.2.1 Controller where I fires a function after 1 second using setTimeout:
onPrint: function () {
var me = this;
// start some stuff here...
// end some stuff here...
// wait 1 second and call checkDownloadComplete function
setTimeout(me.checkDownloadComplete, 1000);
},
checkDownloadComplete: function () {
var me = this;
// validate stuff here...
if (something == "true") {
return;
}
// first approach but didn't work (maybe because the scope)
setTimeout(me.checkDownloadComplete, 1000);
// sencond approach but didn't work
Ext.bind(function () {
setTimeout(checkDownloadComplete, 1000)
}, me)
},
My first try was to use:
setTimeout(me.checkDownloadComplete,1000); But this didn't work
My second try was to comment the last line, and use Ext.bind:
Ext.bind(funciton(){setTimeout(checkDownloadComplete,1000)},me);
Any clue what I'm doing wrong? Because none of those work to call again a function it self.
Thanks
UPDATE:
I also try this but without success:
setTimeout(Ext.bind(checkDownloadComplete,me), 1000);
The solution was to change both setTimeout calls to:
setTimeout(Ext.bind(me.checkDownloadComplete, me), 200);
The problem was that on the first call of the setTimeout, the checkDownloadComplete scope was changed to a setTimeout scope instead of controller scope, so changing both fixed the problem-