-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat(ngModelOptions): allow options to be inherited from ancestors #10922
feat(ngModelOptions): allow options to be inherited from ancestors #10922
Conversation
1060991
to
7edb6b2
Compare
* </div> | ||
* ``` | ||
* | ||
* ... the `input` element will effective have the follow settings ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
effectively ?
following ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, "following" thanks @gkalpak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big deal, but shouldn't effective
be effectively
?
(I am not insisting or anything, just bringing it up again in case you overlooked it; you obviously know better (being a native speaker 😃).)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-) yes. Sorry about that
7edb6b2
to
b99916b
Compare
@@ -1016,7 +1019,11 @@ var ngModelDirective = ['$rootScope', function($rootScope) { | |||
var modelCtrl = ctrls[0], | |||
formCtrl = ctrls[1] || nullFormCtrl; | |||
|
|||
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For readability, could you assign ctrl[2] to a variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do
Looks good so far, but could you mention the modelOptionsProvider in the commit message? |
Btw, in the last paragraph, I am not completely sure what "This would cause the ngModel and input controls below this ngModelOptions directive to display different behaviour." means. Different as in unexpected? Because the option is applied (from ancestor) even though you did not specify it on the current element? |
var parentOptions = ctrls[1] ? ctrls[1].$localOptions : {}; | ||
|
||
// Store the raw options taken from the attributes (after inheriting parent options) | ||
optionsCtrl.$localOptions = extend({}, scope.$eval(attrs.ngModelOptions), parentOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, we all missed that I had the parameters in the wrong order here!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh my ... well you didn't miss it. ;) Should the merging be tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On it
8459419
to
5c22a78
Compare
What is the motivation for this change? From the docs:
This does not seem to fit into any of these categories of unavoidable breaking changes. As the author of an Angular component library, this is massively breaking due to the fact that we export controls that support In all the places that we guard against this, we will now also have to add every possible This seems like something that has small enough value that it should not have been deemed necessary to make in a minor release. |
The main value of this is that you don't have to set your model options for every single input, but can set it either in a parent or in the provider. It's very common that you want the same input behavior for parts of your application. And you can still customize your ngModels per instance. You can even "reset" a sub-part of your application (if you don't know what kind of inputs appear in it for example). At the moment, you need to know the defaults for that, that's true. We could maybe introduce a special value that "resets" the options to the default. I'm also not sure introducing a new option will introduce a BC. A new option will be`optional, so users must make sure their apps (and that includes 3rd party code) are compatible with it when they use it. This is a usual procedure. |
I personally do not feel that this convenience is enough justification for a breaking change.
This breaks my component because the user was able to inject different behavior past my API boundary with that user. We have this all over our library, but for a simple contrived (though realistic) example: <!-- myReusableComponent template -->
Enter text: <input type="text" ng-model="internalModel" ng-model-options="{ updateOn: 'keypress' }"></input>
This should always update instantly: {{ internalModel }}
<button ng-click="writeToModel(internalModel)"></button>
<!-- Application template -->
<my-reusable-component ng-model="userModel" ng-model-options="{ debounce: 100 }"></my-reusable-component> The user's intent was to debounce how often that So basically I'm (as a library author) forced to guard against an Angular "feature", while at the same time my user (app author) needs to be aware of whether or not the libraries they are using properly guard against this (in order to satisfy
If a framework (Angular) changes code that causes my project (library) to no longer work correctly without me doing work, that is by definition a breaking change. If one of my users starts to use a new option somewhere higher up in the DOM, that option will cause my implementation details to behave differently, causing my library to need code changes. |
This is an interesting use case that we had not thought about. We should definitely work something out here before we release 1.6.0. Can I be devil's advocate for a moment...
I wonder if this restriction on adding values to ngModelOptions would be too restrictive in any case? I don't like the idea of adding a "reset" option, since it just adds to the API surface area to work around something in a way that is not intuitive without reading the docs. One option could be that we, somehow, break the inheritance at isolated scope boundaries. Which means that the ngModelOptions will not impact on components that will have complete control over their ngModelOptions values in their templates. I think this could work, although if users could still update values in the global If we cannot find a reasonable solution to this then I agree that we should consider reverting for 1.6.0. |
Another idea would be making the inheritance opt-in. E.g. keep Internally, we will be using the same code with a flag for inheriting vs not inheriting. |
Hmm, @gkalpak, I kind of like it but it makes me feel bit queasy... So then you would have:
Would that work?? |
I'm not a fan of putting another option in the bag. The options affects the ngModel directive, not how the ngModelOptions directive works. It also doesn't make much sense to me that you would have to opt in into the inheritance, because that would defeat the purpose of having to write less code. |
Yeah, I am not a big fan of the extra option either. All other options refer to
Typically, you can save several characters via inheritance, so an extra few character (like |
There was a request to be able to set defaults for The original motivation behind making So I think it is simplest just to revert this feature. WDYT? |
This is something that I have desired in the past to prevent transcluded content from being able to require controllers beyond a boundary (I forget the exact use case now though). Even if it is possible, this sort of things seems too high risk to be worth it IMO.
This seems like probably the best way of accomplishing this goal. I agree it feels weird, but it also seems the most technically correct / least risky way of doing this.
I agree philosophically here, but wouldn't hate this if it made the ergonomics better - though I think Currently a user can accomplish something very similar by putting a function on their scope and writing
I don't really want or need this feature, so from my perspective this is the "best" solution. But any of the opt-in solutions proposed seem reasonable to me if this is a desired feature |
There's a valid use-case for this feature, so I'm not happy with simply reverting it. Introducing a new directive / behavior that achieves inheritance is fine with me. It should be easy to replace ng-model-options with it, so maybe |
…ModelOptions` 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 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.
…ModelOptions` 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.
…ModelOptions` 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.
…ModelOptions` 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.
…ModelOptions` 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.
…ModelOptions` 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.
…ModelOptions` 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.
…ModelOptions` Previously, you had to apply a complete set of `ngModelOptions` at many places in the DOM where you might want to modify just one or two settings. This change allows more general settings to be applied nearer to the root of the DOM and then for more specific settings to inherit 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 #10922 Closes #15389 BREAKING CHANGE: The programmatic API for `ngModelOptions` has changed. You must now read options via the `ngModelController.$options.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. One benefit of these changes, though, is that the `ngModelControler.$options` property is now guaranteed to be defined so there is no need to check before accessing. So, previously: ``` var myOption = ngModelController.$options && ngModelController.$options['my-option']; ``` and now: ``` var myOption = ngModelController.$options.getOption('my-option'); ```
…ModelOptions` Previously, you had to apply a complete set of `ngModelOptions` at many places in the DOM where you might want to modify just one or two settings. This change allows more general settings to be applied nearer to the root of the DOM and then for more specific settings to inherit 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.$options.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. One benefit of these changes, though, is that the `ngModelControler.$options` property is now guaranteed to be defined so there is no need to check before accessing. So, previously: ``` var myOption = ngModelController.$options && ngModelController.$options['my-option']; ``` and now: ``` var myOption = ngModelController.$options.getOption('my-option'); ```
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 at nearer the top of the DOM
and then for more specific settings to override those general settings further down
in the DOM.
It should also be noted that this change means that
ctrl.$options
will always reference a valid objectand so a large part of this PR is removing the unnecessary checks for existence of that object.
BREAKING CHANGE:
Previously, if a setting was not applied on ngModelOptions, then it would default
to undefined. Now the setting will be inherited from the nearest ngModelOptions
ancestor.
It is possible that an ngModelOptions directive that does not set a property,
has an ancestor ngModelOptions that does set this property to a value other than
undefined. This would cause the ngModel and input controls below this ngModelOptions
directive to display different behaviour. This is fixed by explictly setting the
property in the ngModelOptions to prevent it from inheriting from the ancestor.