AngularJS set global ngModelOptions

╄→гoц情女王★ 提交于 2019-11-30 00:43:07

While the ngModel decorators written by Jon/John provide a good behind the scene solution, one must also be aware that declaratively, ngModelOptions need not have to be declared at the individual input field level but can be declared at the module level.

<body ng-app = "myApp" ng-model-options="{ updateOn: 'blur' }">

Doing the above would have all the input fields in myApp module inherit the ng-model-options. These can then be overriden in specific input fields if required (eg. search filters).

This plunker demonstrates: http://plnkr.co/edit/2L1arGgHJwK82xVucJ4p?p=preview

As @Angad mentions you can set ng-model-options on any element, and it will be applied to all its descendants. However, the problem with this is that if you set it like this, radios and checkboxes stop working as expected:

<body ng-app="myApp" ng-model-options="{ updateOn: 'blur' }">

This can be circumvented by adding change and click to the updateOn:

<body ng-app="myApp" ng-model-options="{ updateOn: 'change click blur' }">

If you would also want to update after a certain delay after typing you could use the following:

<body ng-app="myApp" ng-model-options="{ updateOn: 'keyup change click blur', debounce: { keyup: 500, click: 0, change: 0, blur: 0 } }">

I tested these techniques in Firefox, Chome, Internet Explorer (10, 11) and Safari. If you use other events then these, be sure to test it cross browser, for example radios fire change directly after clicking, on all browsers except IE.

This is a really good question so I've written a more indepth blog article about this. The only real generic way I have come up with to do this is to decorate the ngModel directive as it is this directive that really uses the ngModelOptions.

If you look at the angular source for the ngModel directive it has a pre link function to effectively setup the ngModelOptions on the ngModelController through the use of the property $options. Note the $options is created in the ngModelOptionsDirective which is effectively a $eval on the ng-model-options attribute.

What we need to do in our ngModel dectorator is after this pre link function add a default value for this $options property if it is undefined. I am assuming here that if the developer has explicitly set the ngModelOptions in your project that they don't want it magically changed! Therefore we will only set the defaults if the $options property is undefined.

Here is the code:

(function (angular) {
'use strict';

angular.module('myAppOverridesModule').config(['$provide',
    function ($provide) {
        $provide.decorator('ngModelDirective', [
            '$delegate',
            function ($delegate) {
                var directive = $delegate[0],
                    link = directive.link,
                    shouldSetBlurUpdateEvent = function (nodeName, inputType) {
                        // The blur event is only really applicable to input controls so
                        // we want to stick with the default events for selects, checkboxes & radio buttons
                        return nodeName.toLowerCase() === 'textarea' ||
                               (nodeName.toLowerCase() === 'input' && 
                               inputType.toLowerCase() !== 'checkbox' && 
                               inputType.toLowerCase() !== 'radio');
                    };

                directive.compile = function () {
                    return function (scope, element, attrs, ctrls) {
                        var ngModelCtrl = ctrls[0];
                        link.pre.apply(this, arguments);

                        // if ngModelOptions is specified leave it unmodified as developer is explicitly setting it.
                        if (ngModelCtrl.$options === undefined && shouldSetBlurUpdateEvent(element[0].nodeName, element[0].type)) {
                            console.log('set');
                            ngModelCtrl.$options = {
                                updateOn: 'blur',
                                updateOnDefault: false
                            };
                        }

                        link.post.apply(this, arguments);
                    };
                };

                return $delegate;
            }
        ]);
    }
]);
}(angular));

UPDATE: I've updated the code to ignore selects, checkboxes & radio buttons as the blur event is not the optimal update event for them.

I modified Jon Samwell's answer because I couldn't get his to work anymore.

This overrides the ngModelDirective's compile function by storing a reference to it then returning the pre/postLink functions w/ calls to their originals plus our extra override code.

Enjoy!

app.config(function($provide) {

    $provide.decorator('ngModelDirective', function($delegate) {
        var directive = $delegate[0],
            link = directive.link,
            shouldSetBlurUpdateEvent = function (nodeName, inputType) {
              // The blur event is only really applicable to input controls so
              // we want to stick with the default events for selects, checkboxes & radio buttons
              return nodeName.toLowerCase() === 'textarea' ||
                    (nodeName.toLowerCase() === 'input' && 
                     inputType.toLowerCase() !== 'checkbox' && 
                     inputType.toLowerCase() !== 'radio');
                };

        // save a reference to the original compile function
        var compileFn = directive.compile;

        directive.compile = function () {   

            var link = compileFn.apply(this, arguments);

            return {
                pre: function ngModelPostLink(scope, element, attr, ctrls) {

                    if(!ctrls[2]) {
                        ctrls[2] = {};
                    }

                    var ngModelOptions = ctrls[2];

                    if (ngModelOptions.$options === undefined && shouldSetBlurUpdateEvent(element[0].nodeName, element[0].type)) {
                        ngModelOptions.$options = {
                            updateOn: 'blur',
                            updateOnDefault: false
                        };
                    }

                    link.pre.apply(this, arguments);
                },
                post: link.post
            };
        };

        return $delegate;
    });

});

Instead of using

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

you could use

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

and apply it to a parent element in the dom, such as a container div. The debounce setting above tells Angular to only evaluate the validation rules after 500 milliseconds of no activity.

Using debounce this way is superior to using blur because blur creates problems for radio and checkboxes when applied to the entire form.

The debounce option is an integer value which causes the model update to delay an integer number of milliseconds. It reduces the frequency of $digest cycles occurring, which causes the js application to consume less resources as well as allowing the user time to type before validation rules are applied.

YearOfMoo.com recomends using blur and debounce together like this

ng-model-options="{ debounce : { default : 500, blur : 0 } }"

Now model value and validations are applied immediately after the user blurs out of the field. This sets a wait time of 0 ms for the blur event. Blur overrides the default value.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!