Yogesh Jagdale
Yogesh Jagdale

Reputation: 730

ng-model-options debounce not working on blur event as expected?

I have applied ng-model-options on input with following configuration

ng-model-options="{updateOn:'default blur',debounce:{default:1000,blur:0}}"

And as per the applied configuration to the ng-model-options I am expecting the updated ng-model's value on ng-blur event but it doesn't return the new value despite having set 0 debounce value for blur event.

*Note: This problem is occurred only if user focused out before the time given in the default debounce i.e. 1000

HTML:

<input type="text" ng-model="myname" ng-blur="onBlur(myname)" ng-model-options="{updateOn:'default blur',debounce:{default:1000,blur:0}}">
<input type="text" ng-model="output"/>

JS:

$scope.myname = "Yogesh";
$scope.output = "";
$scope.onBlur = function(a){
   $scope.output = a;
}

Plunker link: https://embed.plnkr.co/XJMUUD/

Why debounce is not working? correct me if I am doing wrong any!

Thanks in advance :)

I also have given the answer to my question! let me know how it is flexible to use and how it will help to reduce event digest cycles.

Upvotes: 2

Views: 5512

Answers (3)

Vivz
Vivz

Reputation: 6620

How ng-model-options will help to reduce event digest cycles?

Yes, ng-model-options can help you limit the number of $digest cycles. If you were to use just ng-model without setting any options for it, then your $digest cycle will run for each change in the value of ng-model. If $digest cycle is packed full of data to be dirty-checked, the user is going to see lag in the UI whilst (for instance) typing inside an .Here is an example referenced from toddmotto's blog.

// app.js
angular
	.module('app', []);

function trackDigests($rootScope) {
    function link($scope, $element, $attrs) {
        var count = 0;
        function countDigests(newValue, oldValue) {
            count++;
            $element[0].innerHTML = '$digests: ' + count;
        }
        $rootScope.$watch(countDigests);
    }
    return {
        restrict: 'EA',
        link: link
    };
}

angular
	.module('app')
	.directive('trackDigests', trackDigests);
<script src="//code.angularjs.org/1.4.7/angular.min.js"></script>
<div ng-app="app">
    <div>
        <form name="myForm">
            <h3>Standard &lt;input&gt;</h3>
            <track-digests></track-digests>
            <input 
                   type="text" 
                   name="test" 
                   ng-model="test">
        </form>
    </div>
</div>

As you can see from our standard input, $digest cycle is getting triggered for each character we type in the input field. This may cause end user delay for large applications.

Now we will see the case for inputs with ng-model options.

// app.js
angular
	.module('app', []);

function trackDigests($rootScope) {
    function link($scope, $element, $attrs) {
        var count = 0;
        function countDigests(newValue, oldValue) {
            count++;
            $element[0].innerHTML = '$digests: ' + count;
        }
        $rootScope.$watch(countDigests);
    }
    return {
        restrict: 'EA',
        link: link
    };
}

angular
	.module('app')
	.directive('trackDigests', trackDigests);
<script src="//code.angularjs.org/1.4.7/angular.min.js"></script>
<div ng-app="app">
    <div>
        <form name="myForm">
            <h3>ngModelOptions &lt;input&gt;</h3>
            <track-digests></track-digests>
            <input 
                   type="text" 
                   name="test" 
                   ng-model="test"
                   ng-model-options="{
                       updateOn: 'blur'
                   }">
        </form>
    </div>
</div>

Here we can see that the $digest cycle is getting triggered only when we lose focus from the input. So, basically the ngModelOptions are giving us control over how and when $digest cycles occur.

Let us take even more control over the $digest cycle by introducing debounce so that we can tell angular when to update.

// app.js
angular
	.module('app', []);

function trackDigests($rootScope) {
    function link($scope, $element, $attrs) {
        var count = 0;
        function countDigests(newValue, oldValue) {
            count++;
            $element[0].innerHTML = '$digests: ' + count;
        }
        $rootScope.$watch(countDigests);
    }
    return {
        restrict: 'EA',
        link: link
    };
}

angular
	.module('app')
	.directive('trackDigests', trackDigests);
<script src="//code.angularjs.org/1.4.7/angular.min.js"></script>
<div ng-app="app">
    <div>
        <form name="myForm">
            <h3>ngModelOptions &lt;input&gt;</h3>
            <track-digests></track-digests>
            <input 
                   type="text" 
                   name="test" 
                   ng-model="test"
                   ng-model-options="{
                       updateOn: 'default blur',
                       debounce: {
                           'default': 250,
                           'blur': 0
                       }
                   }">
        </form>
    </div>
</div>

The above illustrates that default will be updated 250ms after the event stops, and blur will update immediately as the user leaves the input (if this is the desired behaviour we want).

Start typing again, then stop and note the $digest count is severely lower than the initial demonstration. You can then click/tab out the to call another $digest immediately.


What is difference between default and change in debounce?

Default and change in debounce objects property are nothing but events. Default is not a DOM event, this is simply part of the ng-model-options api. Suppose you are setting your ngModelOptions like

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

Then there will be no change in the behaviour of your input field from default behaviour. This configuration is not really useful until we combine it with debounce like

ng-model-options="{
  updateOn: 'default',
  debounce: { 'default': 500 }
}"

This will make the input to update after 500ms. So basically this answers what default is. You can use other DOM events like change,blur,mouseover...etc for your debounce.


Update:

When you used ng-model-options="{updateOn:'default blur',debounce:{default:1000,blur:0}}", the ng-blur was getting triggered with the old value of ng-model and after that only the updateOn events were fired.So basically the output will contain the old value of ng-model though the myname will be updated.

Working Example: https://plnkr.co/edit/2JPHXvXd992JJ0s37YC9?p=preview

Now, when you used ng-model-options="{updateOn:'default change blur',debounce:{default:1000,blur:0,change:0}}",the ng-blur was getting triggered with the new value of ng-model because setting change:0 made the updateOn events to fire before ng-blur.So basically the output was updated with the new value of ng-model along with myname.

Working Example : https://plnkr.co/edit/9wdA0he2YVcsPRLJ1Ant?p=preview

Upvotes: 9

Yordan Nikolov
Yordan Nikolov

Reputation: 2678

This is because when you set debounce the digest loop is triggered after the given time. After the digest loop is triggered It checks whether a value has changed that hasn’t yet been synchronized across the app.

In your case the input value will be synchronized with the model variable myname after 1000ms or 1s but immediate update when removing the focus. Your method onBlur(myname) is called with the previous value of myname, because at the time function was called it still has the previous value of the argument passed to it (it can't update the value of myname and call the function at same time) and after that the digest loop update myname. You may check that the model is update immediatly by putting {{myname}} next to the inputs.

ng-blur 
   -> call onBlur(myname)
      -> here myname is with old value still
      -> trigger digest loop (here is where the new value is assigned to myname) 
         -> update model & view

{updateOn: 'event'} specifies that the binding should happen when the specific event occur.

To update the model before your element lose focus (onblur) you have to use updateOn: change and set its time to 0, that's how on each change angular will immediatly bind the new value to your function param.

Upvotes: 1

Yogesh Jagdale
Yogesh Jagdale

Reputation: 730

After some research we came across this configuration

ng-model-options="{updateOn:'default change blur',debounce:{default:1000,blur:0,change:0}}"

Which works fine! as expected on ng-blur event it returns updated value.

Upvotes: 2

Related Questions