-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat(ngModel): bind to getters/setters #7991
Conversation
you are doing a good job 👏 |
@IgorMinar @caitp @matsko – feedback pls. I prefer not to introduce new syntax for the reasons stated above. I'm worried that btford@fa4b121#diff-c244afd8def7f268b16ee91a0341c4b2R1810 might have performance implications. |
the main thing I'm not big on here is the fact that it's specific to forms/ngModel, but maybe it doesn't matter that much. Adding new syntax to the parser would generalize this more, but that's understandably not something we necessarily want to do. I don't think there should be a major performance hit from the type checks because those strings should be interned and most likely reduced to a pointer comparison, but I could be wrong. Branching might not be super helpful, but it probably won't matter in this case. I have some questions though, comments forthcoming |
@@ -1807,7 +1807,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ | |||
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; | |||
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; | |||
|
|||
ngModelSet($scope, ctrl.$modelValue); | |||
var getter = ngModelGet($scope); |
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.
I was going to ask what we're doing with getter
here, because getter(ctrl.$modelValue)
doesn't use the result for anything.
But I realized that this is because this is named getter
, and it should really be renamed to reflect how it's being used here --- setter
or getterSetter
maybe
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.
👍 Agreed on the var name
I think this is reasonable. The main use case I want to address with this approach is where currently you have to write a |
I don't have a lot against it, but I think all of these concerns would be lifted if it was implemented as a syntax enhancement in the parser instead. But I don't have any major concerns with shipping it as is |
What kind of syntax are you thinking? |
I don't have anything in particular in mind, but it would have to clearly indicate that the method should be treated as a getter/setter
I'm not sure --- it doesn't really matter how it would look, so long as it doesn't conflict with other uses, I guess. But it's harder to implement this way, so I don't have major reservations about landing it as is. |
@IgorMinar your input would be appreciated! |
@@ -1820,7 +1820,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ | |||
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; | |||
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; | |||
|
|||
ngModelSet($scope, ctrl.$modelValue); | |||
var getterSetter = ngModelGet($scope); | |||
if (typeof getterSetter === 'function') { |
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.
just use isFunction here
instead of syntax change, how about we add a new ngModelOption option that would specify that the model is a getter/setter? that way there is no perf impact, no confusion, everyone is happy it just requires a bit more typing which should however not be that big of a deal especially given that we are trading it off for less confusion/surprises? |
What about my proposed behavior by default, or you can explicitly provide a Then there is no ambiguity and by default much less typing for users who want this behavior. |
Since ngModelOptions can be inherited from the form you'd need to specify On Fri, Jun 27, 2014, 5:22 PM, Brian Ford [email protected] wrote:
|
I don't think it's worth worrying about those cases I mentioned until someone actually complains about them, but if you want to add such a solution, it probably doesn't hurt to try it |
Hmm. On a semi-related note, there are 3 possible behaviors for any given
See my gross WIP patch above for clarification. I added a new option, |
We decided that the first case in my comment above wasn't ever really desirable. We'll keep the current behavior as a default and expose the new behavior via |
I've implemented the changes as described, but I'd like to improve the documentation (and commit message) around this before it's merged. |
it('should not try to invoke a model if getterSetter is false', function() { | ||
compileInput( | ||
'<input type="text" ng-model="name" '+ | ||
'ng-model-options="{ getterSetter: false }" />'); |
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.
you should also add a test for the default behavior
add the missing default case test + docs and ship it :) |
okay 🍓 |
Done; I'm planning to merge this by EoD unless anyone has additional feedback. |
* A getter/setter is a function that returns a representation of the model when called with zero | ||
* arguments, and sets the internal state of a model when called with an argument. It's sometimes | ||
* useful to use this for models that have an internal representation that's different than | ||
* what the external API exposes. |
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.
I don't really understand what external API means here. Server-Side? Exposed in view?
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.
Good call. I like "exposed to the view" better.
@Narretz: I addressed your comments. Thanks for the feedback! |
* frequently than other parts of your code. | ||
* </div> | ||
* | ||
* You use this behavior by adding `ng-model-options="{ getter: true }"` to an element that has |
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.
Shouldn't be this { getterSetter: true }
?
Same on next line
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.
👍 Good catch!
Landed as b9fcf01. |
@btford this is a great feature but I would like the option to support different types of getters and setters. Not every getter is avalaible as
where if What do you think? |
This is nice, but here is a subtle bug: if you provide This is inconvenient when using function factories, e.g. in the following example
But given the non-assignable limitation, this does not work. |
I feel like trying to support all of these cases (functions returned from other functions returned from other functions and other matryoshka-like behaviour) is not going to be helpful --- I think you could instead use patchMethod when defining your getter/setter to begin with, but there's no point asking ngModel to get the getter/setter by calling it. ngModel already cares about too much stuff already, it would be crazy to give it more things to be concerned with :( Personal opinion, and maybe Brian or others disagree, but I see this as a kind of feature creep, and would prefer if the limitations were documented and solutions prescribed, rather than adding more hacks to ngModel. |
I'm not convinced that the case in @ntrrgc's example is a particularly good idea. |
Is it possible in angular 1.2? Missing support of object.defineProperty in IE8 and dropping support of IE8 in angular 1.3 makes ugly without getter setter support |
This feature is not backported to 1.2 by design because it is a breaking change. If you know what you are doing, you could decorate |
Currently, you might do this if you want to bind to getters/setters:
The implementation in this PR changes the semantics of
ngModel
in the following ways:If the expression bound to
ngModel
resolves to a function, the function is invoked to get the current value to be expressed in the DOM. When the binding changes, if the expression bound tongModel
resolves to a function (at that time) the function is invoked with the new value.This means instead, you could do this:
I like that to end developers, this feels like "uniform access" for common cases. I don't like that this means there's a difference in semantics between
ngModel
and expressions elsewhere in Angular.This would be a breaking change, but I don't think that this would affect any legitimate use cases. The only case I can think of is if hypothetically, you bind to some property that's originally a function, overwriting it with a string. I can't think of any good reason to write controller code like that.
@IgorMinar suggested a different syntax so that the difference in semantics is obvious. Something like:
I like that this makes the semantics obvious. I don't like that this still violates the "uniform access principle."
Closes #768