Skip to content

Commit 2590e94

Browse files
feat(ngModelOptions): allow options to be inherited from ancestor ngModelOptions
Previously, you had to apply the complete set of `ngModelOptions` at every point where you might want to modify just one or two settings. This change allows more general settings to be applied nearer to the top of the DOM and then for more specific settings to override those general settings further down in the DOM. To prevent unwanted inheritance you must opt-in on a case by case basis: * To inherit as single property you simply provide the special value `"$inherit"`. * To inherit all properties not specified locally then include a property `"*": "$inherit"`. Closes angular#10922 Closes angular#15389 BREAKING CHANGE: The programmatic API for `ngModelOptions` has changed. You must now read options via the `ngModelController.getOption(name)` method, rather than accessing the option directly as a property of the `ngModelContoller.$options` object. This does not affect the usage in templates and only affects custom directives that might have been reading options for their own purposes.
1 parent 82f32ec commit 2590e94

File tree

2 files changed

+48
-45
lines changed

2 files changed

+48
-45
lines changed

src/ng/directive/ngModelOptions.js

+32-45
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
11
'use strict';
22

3-
var ngModelOptionsDefaults = {
4-
updateOn: 'default',
5-
debounce: 0,
6-
getterSetter: false,
7-
allowInvalid: false,
8-
timezone: null
9-
};
10-
3+
/* exported $defaultModelOptions */
4+
var $defaultModelOptions;
5+
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
116

127
/**
138
* @ngdoc type
149
* @name ModelOptions
1510
* @description
1611
* A container for the options set by the {@link ngModelOptions} directive
1712
*/
18-
function ModelOptions(options, parentOptions) {
19-
this.$$parentOptions = parentOptions;
20-
this.$$options = extend({}, options); // make a shallow copy
21-
this.$$extendParent();
22-
defaults(this.$$options, ngModelOptionsDefaults);
23-
this.$$processOptions();
13+
function ModelOptions(options) {
14+
this.$$options = options;
2415
}
2516

26-
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
27-
2817
ModelOptions.prototype = {
2918

3019
/**
@@ -46,52 +35,50 @@ ModelOptions.prototype = {
4635
* @return {ModelOptions} a new `ModelOptions` object initialized with the given options.
4736
*/
4837
createChild: function(options) {
49-
return new ModelOptions(options, this.$$options);
50-
},
51-
52-
$$extendParent: function() {
5338
var inheritAll = false;
5439

40+
// make a shallow copy
41+
options = extend({}, options);
42+
5543
// Inherit options from the parent if specified by the value `"$inherit"`
56-
forEach(this.$$options, /* @this */function(option, key) {
44+
forEach(options, /* @this */ function(option, key) {
5745
if (option === '$inherit') {
5846
if (key === '*') {
5947
inheritAll = true;
6048
} else {
61-
this.$$options[key] = this.$$parentOptions[key];
49+
options[key] = this.$$options[key];
6250
}
51+
} else if (key === 'updateOn') {
52+
options.updateOnDefault = false;
53+
options[key] = trim(option.replace(DEFAULT_REGEXP, function() {
54+
options.updateOnDefault = true;
55+
return ' ';
56+
}));
6357
}
6458
}, this);
6559

66-
// We have a property of the form: `"*": "$inherit"`
6760
if (inheritAll) {
68-
delete this.$$options['*'];
69-
defaults(this.$$options, this.$$parentOptions);
61+
// We have a property of the form: `"*": "$inherit"`
62+
delete options['*'];
63+
defaults(options, this.$$options);
7064
}
71-
},
7265

73-
$$processOptions: function() {
74-
var options = this.$$options;
75-
// updateOn and updateOnDefault
76-
if (isDefined(options.updateOn) && options.updateOn.trim()) {
77-
options.updateOnDefault = false;
78-
// extract "default" pseudo-event from list of events that can trigger a model update
79-
options.updateOn = trim(options.updateOn.replace(DEFAULT_REGEXP, function() {
80-
options.updateOnDefault = true;
81-
return ' ';
82-
}));
83-
} else if (this.$$parentOptions) {
84-
options.updateOn = this.$$parentOptions.updateOn;
85-
options.updateOnDefault = this.$$parentOptions.updateOnDefault;
86-
} else {
87-
options.updateOnDefault = true;
88-
}
66+
// Finally add in any missing defaults
67+
defaults(options, $defaultModelOptions.$$options);
68+
69+
return new ModelOptions(options);
8970
}
9071
};
9172

9273

93-
/* exported $defaultModelOptions */
94-
var $defaultModelOptions = new ModelOptions(ngModelOptionsDefaults);
74+
$defaultModelOptions = new ModelOptions({
75+
updateOn: '',
76+
updateOnDefault: true,
77+
debounce: 0,
78+
getterSetter: false,
79+
allowInvalid: false,
80+
timezone: null
81+
});
9582

9683

9784
/**
@@ -154,7 +141,7 @@ var $defaultModelOptions = new ModelOptions(ngModelOptionsDefaults);
154141
* { allowInvalid: true, updateOn: 'default', debounce: 200 }
155142
* ```
156143
*
157-
* Notice that the `debounce` setting now inherits The value from the outer `<div>` element.
144+
* Notice that the `debounce` setting now inherits the value from the outer `<div>` element.
158145
*
159146
* If you are creating a reusable component then you should be careful when using `"*": "$inherit"`
160147
* since you may inadvertently inherit a setting in the future that changes the behavior of your component.

test/ng/directive/ngModelOptionsSpec.js

+16
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,22 @@ describe('ngModelOptions', function() {
164164
dealoc(container);
165165
});
166166

167+
it('should correctly inherit default and another specified event for `updateOn`', function() {
168+
var container = $compile(
169+
'<div ng-model-options="{updateOn: \'default blur\'}">' +
170+
'<input ng-model-options="{\'*\': \'$inherit\'}">' +
171+
'</div>')($rootScope);
172+
173+
var input = container.find('input');
174+
var inputOptions = input.controller('ngModelOptions').$options;
175+
176+
expect(inputOptions.getOption('updateOn')).toEqual('blur');
177+
expect(inputOptions.getOption('updateOnDefault')).toEqual(true);
178+
179+
dealoc(container);
180+
});
181+
182+
167183
it('should make a copy of the options object', function() {
168184
$rootScope.options = {updateOn: 'default'};
169185
var inputElm = helper.compileInput(

0 commit comments

Comments
 (0)