Debouncing ng-keypress - javascript

Does anyone have any pointers for me to debounce the keypress event in angular? I can't get it to debounce. And I know for sure because I'm using $log.debug to print out the keys pressed, and the amount of times it fires off is not at the debounce rate.
I have set it up like this:
<div ng-keypress="doSomething"></div>
and in my controller (not that I have included underscore.js to utilize its debounce method in this instance):
...
$scope.doSomething = function(event, keyEvent) {
var keypressed = String.fromCharCode(keyEvent.which).toUpperCase();
_.debounce(handleKeyPress(keypressed), 500);
}
function handleKeyPress(keypressed) {
//do something with the keypress
}
Thanks for your help in advance.

Try the following code:
$scope.doSomething = _.debounce(function(event, keyEvent) {
$scope.$apply(function() {
// Do something here
});
}, 500);
Working Plunker
As #Enzey said, _.debounce() returns a "debounced" function that needs to be called somewhere to have any effect. And you need to call $apply() in order to trigger a digest cycle. Otherwise any changes on the model made within the debounced function won't update the view.
Update
It turned out that what the OP really wanted was a throttled function. Below is another code snippet using _.throttle():
$scope.doSomething = _.throttle(function($event) {
if ($scope.$$phase) return; // Prevents 'digest already in progress' errors
$scope.$apply(function() {
// Do something here
});
}, 100);

Related

Angular $scope confusion with $watch

I'm still in the beginning stages of my Angular 1.0 journey. I'm learning to like it, but I'm still scratching my head in a few places.
Lately, I've run into something while using $watch that's left me confounded. Take a look:
$scope.$watch('cookies', function() {
if ($cookies.getAll().redditSession) {
$scope.$emit('cookiesChanged')
// $scope.userWelcome = cookieService.decodeCookie($cookies.get('redditSession'))
}
})
$scope.$on('cookiesChanged', function() {
$scope.userWelcome = cookieService.decodeCookie($cookies.get('redditSession'))
})
This code works. If my cookies change, I emit an event thereby triggering the event listener, which changes the value of $scope.userWelcome to some value stored in the cookie. I see this change if I navigate to another route in my app.
However, I'm wondering why I had to use an event emitter here? Notice the line I commented out. I tried this first, but it doesn't change value of $scope.userWelcome, even if I move to another page in my app. I have to reload the page in order to see that I'm logged in.
What's going on here?
Try watching the cookie directly:
$scope.$watch(
function () {
return $cookies.get('redditSession');
},
function (newValue) {
if (newValue) {
$scope.userWelcome = cookieService.decodeCookie(newValue);
};
}
);
Your mistake is that you try to get the new value with the standard method. The way you can actually get the new value is adding it to the parameters of the function. Here it goes:
$scope.$watch('cookies', function(newValue, oldValue) {
if (newValue.getAll().redditSession) {
$scope.userWelcome = cookieService.decodeCookie(newValue.get('redditSession'))
}
// also try this
console.log(oldvalue === $cookies);
});
Cheers!

How to suppress a watch statement or digest cycle in Angular

Currently I have a text input attached to a model with a $scope.watch statement observing the model. This is all used to achieve a type of auto complete / typeahead feature.
<!-- HTML -->
<input type="text" ng-model="search.mySearchText">
// JS
var deregister = $scope.$watch('search.mySearchText', doSearch);
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
// do something with the data
});
}
This works fine. However, occasionally in my .then function I want to make changes to search.mySearchText. Obviously this would cause the watcher to be fired again, but I don't want this.
What I'm hoping to do is find a way to suppress the $watch from firing that next time. Maybe by somehow telling Angular that that particular watched model property is no longer dirty?
I tried removing the $watch by de/re- registering the watch at appropriate times, but that didn't work either.
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
deregister(); // shut off the watch
search.mySearchText = 'some new string'; // manipulate the model property that I don't want to cause a search
deregister = $scope.$watch('search.mySearchText', doSearch);
});
}
However, this didn't prevent the event firing like I expected, which is why I'm now looking for a way to suppress the event.
You could have a variable that determines whether doSearch exits early, like so:
var searchActive = true;
function doSearch() {
if (!searchActive) return;
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
searchActive = false;
// do manipulation of search.mySearchText
searchActive = true;
});
}

angular weirdness with element.click and $location.search

Here is the problem
<div id="my-id" ng-click="moveOn(10)">Click me</div>
In the directive I have
scope.moveOn = function (val) {
$location.search('id', val);
}
And finally in the parent of this directive I listen for a change of this id
$scope.$watch(function () {
return $routeParams.id;
}, function (newId, oldId) {
...
});
This setup works great, after the $location.search is called the $watcher is triggered immediately. But now I also have a directive which does it slightly different, as follows:
element.find('#my-id').click(function (val) {
$location.search('id', val);
});
In the template there is no ng-click!
In this situation I can also see that the call to $location.search is made, but now it takes a very long time (a couple of seconds) before the watcher goes off.
So for some reason there must be a difference between ngClick and binding to a click event. Any suggestions what might be going on here ?
You are updating angular within an event that is outside of angular.
Try using $apply to notify angular of the change so it can run a digest
element.find('#my-id').click(function (val) {
scope.$apply(function(){
$location.search('id', val);
});
});

AngularJS - triggering $watch/$observe listeners

I would like to fire all $watch/$observe listeners even if watched/observed value didn't change. This way I could provide a "testing only" feature to refresh current page/view without user interaction. I've tried to call $apply/$digest but that didn't worked:
$timeout(function(){
$scope.$apply();
});
$timeout(function(){
$scope.$digest();
});
Is there any other way to do it?
Best Regards,
Executing $scope.$apply() will trigger digest cycle as it internally calls $digest, below is example of manual change.
number variable won't get bound as timeout brings it out of angulars scope.
setTimeout(function () {
$scope.number = Math.random();
});
however you can "force" it to show up by manually applying scope changes:
setInterval(function () {
$scope.$apply();
}, 100);
Demos:
No change / Change with manual updates
This will not trigger watchers though. From $digest implementation, it checks if value has changed since the last watch evaluation and will run callback only if it did.
if ((value = watch.get(current)) !== (last = watch.last) ... [rootScope.js]
Therefore you will need somehow change value of the last execution and it's possible to do via $$watchers object on the scope:
$scope.digest = function () {
setTimeout(function () {
angular.forEach($scope.$$watchers, function (w) {
w.last.value = Math.random();
});
$scope.$apply();
});
}
DEMO
How about to use the $emit function then capture that event with $on function?
Within an $emit() event function call, the event bubbles up from the child scope to the parent scope. All of the scopes above the scope that fires the event will receive notification about the event.
We use $emit() when we want to communicate changes of state from within our app to the rest of the application.
_.bind($scope.$emit, $scope, 'myCustomEvent');
then on the capture phase:
$scope.$on('myCustomEvent', function() {
// do something
});

Implement delayed jquery change event handler

What I basically want to achieve is create some kind of delayedChange plugin to be able to call some action (such as ajax call to the server) only after some delay the last input change event was fired. At the moment I've came up with this (jsfiddle). I should see alert only in 5 seconds (5000 msec) the last text change had place but it fires immediately.
(function ($) {
var timer;
$.fn.delayedChange = function (onchange, delay) {
return this.each(function () {
$(this).bind('change', function () {
if (typeof onchange == 'function') {
window.clearTimeout(timer);
timer = window.setTimeout(onchange.call(this), delay);
}
});
});
}
})(jQuery);
$(document).ready(function(){
$('input').delayedChange(function(){
alert($(this).attr('id'));
}, 5000);
});
The weirdest is that this code actually worked for some time, and then it's functionality just vanished for no reason. Obviously there is some explanation but I can't see it for now. Are there some more certain ways to implement/improve such plugin?
The functionality you've described is called "debouncing". Libraries such as underscore, lodash, and ampersand have a debounce method to make this effect convenient.'
With underscore, the code is:
$('input').each(function () {
$(this).on('change', _.debounce(...your function..., 5000));
});
No new function is needed, although you will need to include a new dependency.
I'd made a mistake with the first version. You need to generate a separate debounce function for each element, otherwise changing different elements will cause the timer to reset for all of the elements.

Categories

Resources