AngularJS Debounce Service

HTML

<div ng-app='MyApp' ng-controller="MyCtrl">
    <input type="text" debounce-delay="1000" debounce-model="search"></input>
    <p>{{ val }}</p>
</div>

JavaScript

angular.module('MyApp', [])
    .controller('MyCtrl', ['$scope', '$debounce', function($scope, $debounce) {
        $scope.val = 0;
        
        $scope.search = "";
        $scope.$watch('search', function (newVal, oldVal) {
              if(newVal === oldVal){
        return;
      }
            // called when something in the settings has changed (recursively)
            $scope.val++;
        });
    }])
    // http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
    // adapted from angular's $timeout code
    .factory('$debounce', ['$rootScope', '$browser', '$q', '$exceptionHandler',
        function($rootScope,   $browser,   $q,   $exceptionHandler) {
            var deferreds = {},
                methods = {},
                uuid = 0;

            function debounce(fn, delay, invokeApply) {
                var deferred = $q.defer(),
                    promise = deferred.promise,
                    skipApply = (angular.isDefined(invokeApply) && !invokeApply),
                    timeoutId, cleanup,
                    methodId, bouncing = false;

                // check we dont have this method already registered
                angular.forEach(methods, function(value, key) {
                    if(angular.equals(methods[key].fn, fn)) {
                        bouncing = true;
                        methodId = key;
                    }
                });

                // not bouncing, then register new instance
                if(!bouncing) {
                    methodId = uuid++;
                    methods[methodId] = {fn: fn};
                } else {
                    // clear the old timeout
                    deferreds[methods[methodId].timeoutId].reject('bounced');
                    $browser.defer.cancel(methods[methodId].timeoutId);
                }

                var debounced = function() {
                    // actually executing? clean method bank
                    delete methods[methodId];

                ...