5

I'm developing an application with angularjs, which shows some textfields in the screen with numeric data. They look quite like this:

<input type="text" ng-model="value" ng-change="controller.functions.valueChanged(value)">

The problem is everytime I write a number or I delete a number from the textfield, ng-change directive calls to the function. Is it possible to apply some king of delay to ng-change function?

0

4 Answers 4

21

You can use ngModelOptions

debounce: integer value which contains the debounce model update value in milliseconds. A value of 0 triggers an immediate update.

Code

 <input type="text" ng-model-options="{ debounce: 1000 }" ng-model="value" ng-change="controller.functions.valueChanged(value)">
Sign up to request clarification or add additional context in comments.

1 Comment

@m.dorian - This answer is much better. I think you should change the accepted answer to this one.
7

Updated

you can use $timeout service to create delay function. this can be applied to other directive callback

angular.module('myApp', []);
angular.module('myApp')
  .controller('myCtrl', ["$scope", "$log", "$timeout",
    function($scope, $log, $timeout) {

      $scope.delay = (function() {
        var promise = null;
        return function(callback, ms) {
          $timeout.cancel(promise); //clearTimeout(timer);
          promise = $timeout(callback, ms); //timer = setTimeout(callback, ms);
        };
      })();

      $scope.doSomeThing = function(value) {
        var current = new Date();
        $scope.result = 'value:' + $scope.foo + ', last updated:' + current;
      };

    }
  ]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
  <h3>$timeout delay demo</h3>
  <div>
    <input ng-model="foo" ng-change="delay(doSomeThing, 1000)" type="text" />
  </div>
  <div>Result: {{result}}</div>
</div>

3 Comments

I've implemented the method just as it is here, and i've incremented the delay up to 3000 and does anything, are you sure it's ok?
updated demo in code snippet. a callback function should parse as args.
Seems better and is it possible to send params to doSomething? In the ng-change call it looks like a parameter and not like a function call
3

I am using AngularJs 1.2.x and stumble upon the ng-change issue of firing on each change. ng-blur can be used but it fires even though there is no change in the value. So both cannot be used efficiently.

With Angularjs 1.3.x, things are easier using ng-model-options like below

to invoke change function "onBlur"

ng-change="ctrl.onchange()" ng-model-options="{updateOn: 'blur'}"

And

to delay invocation of change function by 500ms

ng-change="ctrl.onchange()" ng-model-options='{ debounce: 500 }'"

Now coming to back to the question of getting such things with AngularJs 1.2.x

to invoke change function "onBlur"

html

<input type="text" ng-model="ctrl.a.c" sd-change-on-blur="ctrl.onchange()" /> or

<input type="text" ng-model="ctrl.a.c" sd-change-on-blur="ctrl.onchange(ctrl.a.c)" />

JS

app.directive('sdChangeOnBlur', function() {
  return {
    restrict: 'A',
    scope: {
      sdChangeOnBlur: '&'
    },
    link: function(scope, elm, attrs) {
      if (attrs.type === 'radio' || attrs.type === 'checkbox')
        return;

      var parameters = getParameters(attrs.sdChangeOnBlur);

      var oldValue = null;
      elm.bind('focus', function() {
        scope.$apply(function() {
          oldValue = elm.val();
        });
      })
    
      elm.bind('blur', function() {
        scope.$apply(function() {
          if (elm.val() != oldValue) {
            var params = {};
            if (parameters && parameters.length > 0) {
              for (var n = 0; n < parameters.length; n++) {
                params[parameters[n]] = scope.$parent.$eval(parameters[n]);
              }
            } else {
              params = null;
            }

            if (params == null) {
              scope.sdChangeOnBlur();
            } else {
              scope.sdChangeOnBlur(params)
            }
          }
        });
      });
    }
  };
});

function getParameters(functionStr) {
  var paramStr = functionStr.slice(functionStr.indexOf('(') + 1, functionStr.indexOf(')'));
  var params;
  if (paramStr) {
    params = paramStr.split(",");
  }
  var paramsT = [];
  for (var n = 0; params && n < params.length; n++) {
    paramsT.push(params[n].trim());
  }
  return paramsT;
}

to delay invocation of change function by 500ms

html

<input type="text" ng-model="name" sd-change="onChange(name)" sd-change-delay="300"/>

OR

<input type="text" ng-model="name" sd-change="onChange()" sd-change-delay="300"/>

JS

app.directive('sdChange', ['$timeout',
  function($timeout) {
    return {
      restrict: 'A',
      scope: {
        sdChange: '&',
        sdChangeDelay: '@' //optional
      },
      link: function(scope, elm, attr) {
        if (attr.type === 'radio' || attr.type === 'checkbox') {
          return;
        }

        if (!scope.sdChangeDelay) {
          scope.sdChangeDelay = 500; //defauld delay
        }

        var parameters = getParameters(attr.sdChange);

        var delayTimer;
        elm.bind('keydown keypress', function() {
          if (delayTimer !== null) {
            $timeout.cancel(delayTimer);
          }

          delayTimer = $timeout(function() {
            var params = {};
            if (parameters && parameters.length > 0) {
              for (var n = 0; n < parameters.length; n++) {
                params[parameters[n]] = scope.$parent.$eval(parameters[n]);
              }
            } else {
              params = null;
            }

            if (params == null) {
              scope.sdChange();
            } else {
              scope.sdChange(params)
            }
            delayTimer = null;
          }, scope.sdChangeDelay);

          scope.$on(
            "$destroy",
            function(event) {
              $timeout.cancel(delayTimer);
              console.log("Destroyed");
            }
          );
        });
      }
    };
  }
]);

function getParameters(functionStr) {
  var paramStr = functionStr.slice(functionStr.indexOf('(') + 1, functionStr.indexOf(')'));
  var params;
  if (paramStr) {
    params = paramStr.split(",");
  }
  var paramsT = [];
  for (var n = 0; params && n < params.length; n++) {
    paramsT.push(params[n].trim());
  }
  return paramsT;
}

plnkrs for both approaches are

http://plnkr.co/edit/r5t0KwMtNeOhgnaidKhS?p=preview

http://plnkr.co/edit/9PGbYGCDCtB52G8bJkjx?p=info

Comments

2

The easiest way to do it is to set a timeout inside the controller.functions.valueChanged function.

Angularjs has ngModelOptions directive which is very useful for this kind of things. You can try to set

ng-model-options="{ debounce: 1000 }"

for the timeout before the model changes. You can also use

ng-model-options="{ updateOn: 'blur' }"

To update the model only when focus leaves the element.

1 Comment

If I do it with ng-model-options, model waits to change but the controller.functions.valueChanged functions is called instantly when i change something in textfield, so it doesn't work for me. How can i add a timeout in the function?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.