Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Make $debounce service part of angular !? #2690

Closed
honzajde opened this issue May 17, 2013 · 30 comments
Closed

Make $debounce service part of angular !? #2690

honzajde opened this issue May 17, 2013 · 30 comments

Comments

@honzajde
Copy link

I am simply watching some objects on my scope that are changed as part of several digest cycles. After digesting them (changing their values via databinding) has finished, I want to save them to databse, but only ONCE and that is the point of the $debounce service.

From asking this question on SO, http://stackoverflow.com/questions/16278216/angularjs-save-changes-after-digest-has-finished, I didn't see that this common use-case that will once arise for anyone doing something more complex, is clearly solved.

So I came with this:

/**
 * Service function that helps to avoid multiple calls of a function (typically save()) during angular digest cycle.
 * $apply will be called after original function returns;
 *
 * Use it like this:
 *
 *  $scope.$watch('order', function(newOrder){
 *     $scope.orderRules.apply(newOrder); // changing properties on order
 *   }, true);
 *
 *  $scope.$watch('order.valid', function(newOrder){
 *     $scope.save(newOrder); //will be called multiple times while digested by angular
 *   });
 *
 *  $scope.save = debounce(function(order){
 *     // POST your order here ...$http....
 *     // debounce() will make sure save() will be called only once
 *   });
 */
define(['app'], function (app) {
    app.factory('debounce', ['$timeout', function ($timeout) {
        return function(fn, timeout, apply){ // debounce fn
            timeout = angular.isUndefined(timeout) ? 0 : timeout;
            apply = angular.isUndefined(apply) ? true : apply; // !!default is true! most suitable to my experience
            var nthCall = 0;
            return function(){ // intercepting fn
                var that = this;
                var argz = arguments;
                nthCall++;
                var later = (function(version){
                    return function(){
                        if (version === nthCall){
                            return fn.apply(that, argz);
                        }
                    };
                })(nthCall);
                return $timeout(later, timeout, apply);
            };
        };
    }]);
});

Question 1 to you: Do you see the need for such service?
Question 2 to you: Is this the optimal solution?

@MikeMcElroy
Copy link
Contributor

Is it best practice to save to your back-end whenever a $watch is triggered
(whenever the item changes), or to have the user complete an action before
transmitting that data over the wire? (eg. an ng-click on a "Save" icon or
button)

On Fri, May 17, 2013 at 10:07 AM, honza [email protected] wrote:

I am simply watching some objects on my scope that are changed as part of
several digest cycles. After digesting them (changing their values via
databinding) has finished, I want to save them to databse, but only ONCE
and that is the point of the $debounce service.

From asking this question on SO,
http://stackoverflow.com/questions/16278216/angularjs-save-changes-after-digest-has-finished,
I didn't see that this common use-case that will once arise for anyone
doing something more complex, is clearly solved.

So I came with this:

/** * Service function that helps to avoid multiple calls of a function (typically save()) during angular digest cycle. * $apply will be called after original function returns; * * Use it like this: * * $scope.$watch('order', function(newOrder){ * $scope.orderRules.apply(newOrder); // changing properties on order * }, true); * * $scope.$watch('order.valid', function(newOrder){ * $scope.save(newOrder); //will be called multiple times while digested by angular * }); * * $scope.save = debounce(function(order){ * // POST your order here ...$http.... * // debounce() will make sure save() will be called only once * }); */define(['app'], function (app) {
app.factory('debounce', ['$timeout', function ($timeout) {
return function(fn, timeout, apply){ // debounce fn
timeout = angular.isUndefined(timeout) ? 0 : timeout;
apply = angular.isUndefined(apply) ? true : apply; // !!default is true! most suitable to my experience
var nthCall = 0;
return function(){ // intercepting fn
var that = this;
var argz = arguments;
nthCall++;
var later = (function(version){
return function(){
if (version === nthCall){
return fn.apply(that, argz);
}
};
})(nthCall);
return $timeout(later, timeout, apply);
};
};
}]);});

Question 1 to you: Do you see the need for such service?
Question 2 to you: Is this the optimal solution?


Reply to this email directly or view it on GitHubhttps://github.com//issues/2690
.

@kstep
Copy link
Contributor

kstep commented May 20, 2013

@MikeMcElroy I can see reasons for both behaviors, and both of them can be named "best practices" depending on application being implemented. I think developer should have an option here (as well as customer at the end, as he pairs the money).

@pstadler
Copy link

pstadler commented Aug 7, 2013

👍

Great for delaying AJAX requests on input changes for features like autocomplete and suggestions.

@lrlopez
Copy link
Contributor

lrlopez commented Aug 7, 2013

@pstadler, I proposed in PR #2129 a way to extend $timeout with a debounce feature. It is used to implement delayed model updates on input controls. That may be useful for your use-case...

@pstadler
Copy link

pstadler commented Aug 8, 2013

@lrlopez I first met the concept in bacon.js. I'm new to the angular circus and was really wondering that there's no debounce feature built-in (e.g. alongside $timeout).
About #2129: I don't yet have the experience to figure out what negative impact the delaying of model updates might has.

@lrlopez
Copy link
Contributor

lrlopez commented Aug 8, 2013

Shouldn't be much of an issue if you're careful (i.e. don't allow submitting instantly unless you also specify submit as an update model event with 0 delay).

The idea behind delaying the model update is that any watch you bind to it will be called only once even if there are lots of changes in a row. If you use the model for filtering or for making AJAX requests, load will be reduced.

You could also attach a database save to a model watch to achieve the same behaviour exposed in the first post.

@cm325
Copy link

cm325 commented Sep 24, 2013

👍

@ghost ghost assigned btford Dec 19, 2013
@ThomasWeiser
Copy link

+1

@thatmarvin
Copy link

+1

I like it as a service since it's more flexible.

@michalkvasnicak
Copy link

+1

10 similar comments
@erwinmombay
Copy link

+1

@builtbylane
Copy link
Contributor

+1

@ghost
Copy link

ghost commented Dec 27, 2013

+1

@artem-popov
Copy link

+1

@amfern
Copy link

amfern commented Jan 13, 2014

+1

@jo-m
Copy link

jo-m commented Jan 13, 2014

+1

@BeheadedKamikaze
Copy link

+1

@stuart-harris
Copy link

+1

@LexMalta
Copy link

+1

@seanzx85
Copy link

+1

@semi-sentient
Copy link

+1

We're currently using a directive that wraps _.debounce to accomplish delayed searches on text inputs, but it would be great if this were a native directive (ngDebounce).

@nervgh
Copy link

nervgh commented Jan 24, 2014

@honzajde honzajde mentioned this issue Jan 26, 2014
@honzajde
Copy link
Author

I have just seen Karl Seamon's talk on ng-conf 2014, where he mentions $$postDigest event .. another approach to the same issue. He said these matters are being discussed:) See: #5828.

@leblancmeneses
Copy link

my usage is outside ng-form ng-submit usecase for debounce - I've added the code to stackoverflow original posting:http://stackoverflow.com/questions/13320015/how-to-write-a-debounce-service-in-angularjs/22056002#22056002

@shahata
Copy link
Contributor

shahata commented Mar 21, 2014

I just published a nice implementation of both a debounce service and directive that can work with any ng-model at: https://github.com/shahata/angular-debounce

Or simply install it using: bower install ng-debounce
I don't see any reason this should be included in angular core, it's not a very common use case.

@q0rban
Copy link

q0rban commented Mar 21, 2014

Very nice, well documented work, @shahata!

@fidoboy
Copy link

fidoboy commented Mar 21, 2014

@shahata how can i modify this to work with ngChange attribute instead (and not with ngModel)?

@caitp
Copy link
Contributor

caitp commented Mar 21, 2014

@fidoboy ngChange requires ngModel (it doesn't make sense without it), all it does is register a listener with ngModel to be called when the view value changes.

Anyways, as for this not being included in angular core, actually this is going to end up in angular core (pete has been working on an implementation as part of an initiative to address some requests for ngModel), so, it's pretty much going to happen.

@petebacondarwin
Copy link
Contributor

As of current master branch ngModel has debouncing built in via the ngModelOptions directive. Thanks to @lrlopez, @auser, @shahata and others. If you need further debouncing in your app you might want to look to @shahata's implementation as it does more.

@mikila85
Copy link

+1

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests