Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(ngValue): setting binding to undefined should update the DOM value to an empty string #15605

Closed
wants to merge 8 commits into from

Conversation

frederikprijck
Copy link
Contributor

@frederikprijck frederikprijck commented Jan 12, 2017

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Bug fix

What is the current behavior? (You can also link to an open issue here)
Setting the binding to undefined calls jqLite/jQuery's prop() function, passing undefined as the second argument: element.prop('value', undefined).
This is identical to element.prop('value') and will retrieve the dom value instead of updating it.

See: #15603

What is the new behavior (if this is a feature change)?

Does this PR introduce a breaking change?
No.

Please check if the PR fulfills these requirements

Other information:

…e to an empty string

Previously, when setting the binding to undefined would call jqLite/jQuery's `prop()` function, passing undefined as the second argument: `element.prop('value', undefined)`.
This is identical to `element.prop('value') and will retrieve the dom value instead of updating it.

This commit will prevent from undefined being passed as a second argument by passing `null` instead.

Fixes: 15603
Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IE doesn't approve yor changes 😃

});

expect(inputElm[0].value).toBe('');
expect(inputElm[0].getAttribute('value')).toBe(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out that in IE you can't remove the value attribute (🥂). This is not related to this issue though and at this point I don't think it is worth fixing (since nobody has complained about it and it is actually a browser issue).

You can skip this expectation in IE, by checking the msie property:

// Support: IE 9-11
// In IE it is not possible to remove the `value` attribute from an input element.
if (!msie) {
  expect(inputElm[0].getAttribute('value')).toBeNull();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I do it this way, or just get rid of the except as this has nothing to do with my change ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to keep the expectation on other browsers (even if it is not directly related to this PR, it is still relevant for the added test).

@@ -2142,7 +2142,7 @@ var ngValueDirective = function() {
* makes it possible to use ngValue as a sort of one-way bind.
*/
function updateElementValue(element, attr, value) {
element.prop('value', value);
element.prop('value', angular.isUndefined(value) ? null : value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea (to avoid the arbitrary conversion to null) is to change to element[0].value = value. (I am not sure what is better.) It will not matter in most cases (e.g. when used with native inputs), only if someone is using it with a custom input.

It turns out that in IE9 input.value = null results in input.value === 'null' (same for undefined). So we have a problem there.

It matters in cases where value in always undefined (so updateElementValue will only run once at the beginning). Previously, element.prop('value', undefined) would be ignored (and the initial value will be kept), but now it will be set to 'null' (or 'undefined').

We could check for IE9 and set the value to '' instead (and also add a "known issue" in the docs):

var propValue = angular.isDefined(value) ? value : (msie === 9) ? '' : null;
element.prop('value', propValue);

(It is not ideal, but it is a good middleground imo - plus it will only affect IE9 users.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update the PR with the msie check and using isDefined instead of isUndefined.

If I get this straight, using element[0].value = propValue should be possible aswell? The difference is we're not using jQuery/jqLite in this case, which may be a better idea but I have no idea what you guys prefer...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am 99% sure it won't make a difference in this particular case, but let's stick to .prop() (because...it doesn't make a difference).

BTW, I forgot to mention, you don't need the angular. prefix; just is[Un]Defined(...).

Copy link
Contributor Author

@frederikprijck frederikprijck Jan 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

So for the docs part:

Setting the value to undefined will update the DOM value to null.

IE9: updating the DOM value to null results in 'null'. Setting the value to undefined will update the DOM value to '' instead of null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving out angular. resulted in a broken build. But it looks like it's not related to that ?

it('should update the dom "value" to "" and attribute to null when the binding is set to undefined', function() {
var inputElm = helper.compileInput('<input type="text" ng-value="value">');

$rootScope.$apply('value = \'something\'');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Feel free to use " inside string expressions 😁 (E.g.: 'value = "something"')

expect(inputElm[0].getAttribute('value')).toBe('something');

$rootScope.$apply(function() {
delete $rootScope.value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This could be $rootScope.$apply('value = undefined'). (just sayin'...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not going to fix this nit as I prefer delete. If you prefer me to use undefined anyway, I'll update it!

@@ -4211,6 +4211,22 @@ describe('input', function() {
expect(inputElm[0].getAttribute('value')).toBe('something');
});

it('should update the dom "value" to "" and attribute to null when the binding is set to undefined', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say something like:

should clear the value attribute and property when the value is undefined

Copy link
Contributor Author

@frederikprijck frederikprijck Jan 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why I took this was because this test has a comparable way of describing itself:
https://github.com/angular/angular.js/blob/master/test/ng/directive/inputSpec.js#L4205

So combining my solution with yours, I'd propose:

Should clear the "dom" value property and attribute when the value is undefined.

Yet I do think this being somehow odd, talking about clearing both feels like having the same result for both. However, dom value is set to '' whereas attr is set to null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should clear the "dom" value property and attribute when the value is undefined.

👍

Yet I do think this being somehow odd, talking about clearing both feels like having the same result for both. However, dom value is set to '' whereas attr is set to null.

"Clear" is generic and can mean different things for different...things (e.g. property vs attribute). What we clear is an implementation detail - it doesn't have to go to the test description imo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@frederikprijck
Copy link
Contributor Author

frederikprijck commented Jan 13, 2017

I'm sorry for the huge amount of fixup's btw ...

@@ -2142,7 +2142,8 @@ var ngValueDirective = function() {
* makes it possible to use ngValue as a sort of one-way bind.
*/
function updateElementValue(element, attr, value) {
element.prop('value', value);
var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a good idea to add a comment here explaining the IE9 issue. Something like:

// Support: IE9 only
// In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).

@gkalpak
Copy link
Member

gkalpak commented Jan 13, 2017

I'm sorry for the huge amount of fixup's btw ...

I'm sorry for the large amount of comments 😛

@gkalpak
Copy link
Member

gkalpak commented Jan 13, 2017

@Narretz, I could use an extra pair of eyes here 😉

@Narretz
Copy link
Contributor

Narretz commented Jan 16, 2017

Looks like you already worked out the kinks here :)

In the first commit, you talked about updating the docs because of IE9:

Setting the value to undefined will update the DOM value to null.
IE9: updating the DOM value to null results in 'null'. Setting the value to undefined will update the DOM value to '' instead of null.

I think it doesn't hurt to add this.
However, if we add should we also add code / tests that ensure that we set the value to null and '' (IE9) when setting the value to null? Atm we only so this for undefined.

@frederikprijck
Copy link
Contributor Author

I had a chat with @gkalpak offline to leave out the docs part. As this wasn't part of the reported issue I'm fixing here.

I'm happy to still add that if required tho.

@gkalpak
Copy link
Member

gkalpak commented Jan 17, 2017

There are three things that could be documented and one issue that potentially needs fixing:

  1. docs: The fact that we use node.prop = null, when value is undefined.
  2. docs: The fact that we use node.prop = '', when `value is undefined in IE9.
  3. docs: The fact that we node.prop === 'null', when value is null` in IE9.
  4. fix: The fact that node.prop === 'null', when value is null in IE9.

(4) is unrelated to this PR, so no need to discuss further (except for saying that it could be considered a BC and is not worth fixing imo).

WRT (1) and (2): These should not make any difference in the majority of cases (the result will be node.prop === '' anyway), so I am afraid that mentioning them in the docs might be more confusing and "upsetting" than helpful. It didn't work correctly before anyway, so if anything this is an improvement.

WRT (3): This is unrelated to this PR, so documentng shouldn't necessarily be done as part of it. Generally, the issue itself only affects IE9 (so only a small number of users) and strictly speaking it is a browser issue (not an Angular issue); i.e. the same would happen if you did node.prop = null. So, I would say Angular works as expected here.

That said, I don't feel too strongly about any of this - I am fine documenting/fixing (not necessarily in this PR) if @Narretz we should.

@Narretz
Copy link
Contributor

Narretz commented Jan 18, 2017

@gkalpak Thanks for the thorough write-up. Please go ahead and merge. If we need these details later in the docs, we can just take them from your comment :)

@gkalpak gkalpak closed this in d85b3f5 Jan 18, 2017
gkalpak pushed a commit that referenced this pull request Jan 19, 2017
…ndefined

Previously, when the expression evaluated to `undefined` the `value` property
was not updated. This happened because jqLite/jQuery's `prop(_, undefined)` is
treated as a getter, thus not apdating the property.

This commit fixes it by setting the property to `null` instead.
(On IE9 we use `''` - see inline comments for more info.)

Fixes #15603

Closes #15605
@frederikprijck frederikprijck deleted the fix/15603 branch January 30, 2017 18:18
ellimist pushed a commit to ellimist/angular.js that referenced this pull request Mar 15, 2017
…ndefined

Previously, when the expression evaluated to `undefined` the `value` property
was not updated. This happened because jqLite/jQuery's `prop(_, undefined)` is
treated as a getter, thus not apdating the property.

This commit fixes it by setting the property to `null` instead.
(On IE9 we use `''` - see inline comments for more info.)

Fixes angular#15603

Closes angular#15605
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants