2

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.

Yogesh Jagdale
  • 721
  • 9
  • 21
  • please refer the plunker I have provided! – Yogesh Jagdale Jul 11 '17 at 18:47
  • Looks like the second input is updated with previous model value even after removing `debounce` from `ng-model-options` like `ng-blur="onBlur(myname)" ng-model-options="{updateOn:'blur'}"` – Stanislav Kvitash Jul 11 '17 at 19:11
  • yes @StanislavKvitash I also tested. It means that its issue of `ng-model-options="{updateOn:'blur'}"`, actually it like bug in the `ng-model-options` directive. because unnecessarily it leads to use `$timeout` in the directive or controller! – Yogesh Jagdale Jul 12 '17 at 04:01
  • 1
    I've tried to debug this yesterday and it looks like `on("blur"` listener which was created by `ngBlur` fires earlier than the view value was committed and the model value was changed by the `updateOn` listener. Like a workaround I think `ngChange` could be used (since it fires when the model was actually changed) instead of `ngBlur`. – Stanislav Kvitash Jul 12 '17 at 09:07
  • if you do not want to add `change` inside the default, you can go by JavaScript, for input [type="text"] only. `` – Sachink Jul 15 '17 at 14:56

3 Answers3

9

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

Vivz
  • 6,625
  • 2
  • 17
  • 33
  • the above `ng-model-options="{updateOn:'default blur',debounce:{default:250,blur:0}}"` will not update `ng-model` immediately! you can check! – Yogesh Jagdale Jul 18 '17 at 12:23
  • 1
    No it will not , it will be updated only after 250ms or if you move your focus out of input. – Vivz Jul 18 '17 at 12:25
  • Actually I want to say that... it will not update `ng-model` immediately on `ng-blur` event. I want my `ng-model` to be updated immediately on `ng-blur` event. which is not happening!! – Yogesh Jagdale Jul 18 '17 at 12:32
  • 1
    I thought you had found a solution for this. And you wanted to know about how it is flexible to use and how it will help to reduce event digest cycles. And what is difference between default and change in debounce – Vivz Jul 18 '17 at 12:35
  • 1
    If you want to update the values use ng-change because ng-blur will trigger with the old value if you don't wait for 1 sec. Check https://plnkr.co/edit/ZnF411h5z08NDr7OdK3e?p=preview – Vivz Jul 18 '17 at 12:40
  • yes!... Previously I had `ng-model-options="{updateOn:'default blur',debounce:{default:1000,blur:0}}"` configuration and now I have `ng-model-options="{updateOn:'default change blur',debounce:{default:1000,blur:0,change:0}}"` configuration, which have included `change:0` in it, So now what `change:0` have made the difference into the events that I want to know! Since I was asked that what is diff betw `change` and `default` in debounce..! – Yogesh Jagdale Jul 18 '17 at 12:43
  • Setting change:0 made the ng-model to update immediately.When u move ur focus out of the input box irrespective of the default value, the ng-model is internally updated coz of change:0. Previously even if u set blur:0, the ng-model will not be updated if u move out of focus within 1 sec. This is because the updateOn for blur will fire with the old value of ng-model – Vivz Jul 18 '17 at 12:49
  • So it means that `change:0` is overriding `default`'s debounce timeout? – Yogesh Jagdale Jul 18 '17 at 12:52
  • I have updated my answer with explanation of events within 1 sec. Observe the {{myname}} in the above two examples. @YogeshJagdale – Vivz Jul 18 '17 at 13:04
  • Thank you @Vivz to explaining this much!! ... So what `change:0` will increase `$digest` cycles? – Yogesh Jagdale Jul 18 '17 at 13:11
  • can you please update this into your answer in the bold! this is very vital thing in the `ng-model-options` directive by my understanding! – Yogesh Jagdale Jul 18 '17 at 13:17
  • Adding `change:0` will increase $digest cycles!! – Yogesh Jagdale Jul 18 '17 at 13:30
  • See setting change:0 will not increase $digest cycles more than normal scenario. It might be more compared to blur or default is what I meant. But whatever event you use except for using default alone will increase your performance than normal scenario. – Vivz Jul 18 '17 at 16:58
  • You can test the $digest cycles of your two configurations here https://jsfiddle.net/x3fp5a4z/1/ and https://jsfiddle.net/4jmscrh4/. You can see that one with change event is slightly better than one without it. (Not much of a difference though) – Vivz Jul 18 '17 at 19:42
2

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.

Yogesh Jagdale
  • 721
  • 9
  • 21
1

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.

Yordan Nikolov
  • 2,598
  • 13
  • 16
  • thank you for reply!! but what does the 0 debouce value for blur is mean here then?? – Yogesh Jagdale Jul 11 '17 at 18:49
  • It means I need to use `$timeout`. basically I expecting my work should be done without using `$timeout`. don't you think this is bug? – Yogesh Jagdale Jul 12 '17 at 04:10
  • if you want to use the variable from `ng-model` on which you have `ng-model-options` as a function param., yes probably you have to use `$timeout`. I don't think that is some kind of bug I think this is how it behave. – Yordan Nikolov Jul 12 '17 at 07:49
  • Okey! for small application this change is very little but for big application needs to change at every point so we are looking for the solution which best fit our expectation :). – Yogesh Jagdale Jul 12 '17 at 08:10