-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix($interpolate): use custom toString() function & fix(ngBind): use same json conversion as $interpolate #14715
Conversation
@@ -60,7 +60,7 @@ var ngBindDirective = ['$compile', function($compile) { | |||
$compile.$$addBindingInfo(element, attr.ngBind); | |||
element = element[0]; | |||
scope.$watch(attr.ngBind, function ngBindWatchAction(value) { | |||
element.textContent = isUndefined(value) ? '' : value; | |||
element.textContent = isUndefined(value) ? '' : stringify(value); |
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.
This can be simplified to:
element.textContent = stringify(value);
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.
Can you include this change too @Narretz ?
I think it makes sense to make I think a good compromise would be to use |
I agree with @gkalpak that we should use |
We found a Contributor License Agreement for you (the sender of this pull request) and all commit authors, but as best as we can tell these commits were authored by someone else. If that's the case, please add them to this pull request and have them confirm that they're okay with these commits being contributed to Google. If we're mistaken and you did author these commits, just reply here to confirm. |
1 similar comment
We found a Contributor License Agreement for you (the sender of this pull request) and all commit authors, but as best as we can tell these commits were authored by someone else. If that's the case, please add them to this pull request and have them confirm that they're okay with these commits being contributed to Google. If we're mistaken and you did author these commits, just reply here to confirm. |
I've updated the PR to include the commit that calls custom toString() on objects. PTAL. I've noticed a problem with our own date input tests. In the original PR, Date objects als had toString() called when interpolating them. This change made our tests fail that allow min and max attributes of date inputs to be date objects: angular.js/test/ng/directive/inputSpec.js Line 1896 in f3377da
'"2014-11-09T23:00:00.000Z"' . Date.toString() however produces a locale aware string that looks like this: Mon Nov 10 2014 00:00:00 GMT+0100 (Mitteleuropäische Zeit),
As a fix, I've updated the createDateParser function ( angular.js/src/ng/directive/input.js Line 1250 in f3377da
There's also the issue that ngMessageFormat duplicates the stringify function, and at the moment all our unit tests will use this function instead of the one defined in Angular.js. This is a bit weird, but at least it guarantees that you notice when you have made changes to the core implementation. |
value = '' + value; | ||
break; | ||
default: | ||
if (isFunction(value.toString) && value.toString !== Object.prototype.toString && !isArray(value)) { |
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 think we already have toString
as an "IIFE global".
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.
We do have a hasCustomToString fn
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.
Even better 😃
expect(element.text()).toEqual(prop[1]); | ||
}); | ||
}); | ||
|
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 would add a test with a custom toString
here too.
Perhaps we should move |
Actually there already IS a |
Yes, but we need to share between ng and ngMessageFormat and it looks like ngMessageFormat only needs the stringify and not the other functions |
PR has been updated, tests pass, PTAL |
@@ -78,7 +78,7 @@ describe('ngBind*', function() { | |||
$rootScope.bind = new Date(2014, 10, 10, 0, 0, 0); | |||
element = $compile('<div ng-bind="bind"></div>')($rootScope); | |||
$rootScope.$digest(); | |||
expect(element.text()).toEqual('"2014-11-09T23:00:00.000Z"'); | |||
expect(element.text()).toBe('"' + $rootScope.bind.toJSON() + '"'); |
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.
Unrelated to this change but, is it me, or does it seem weird to use a property called bind
in this test?
The nitpick was #14715 (comment) but it looks like it is dealt with. LGTM |
I think respecting a custom toString fn could be a breaking change for some. I've also marked the ngBind change as breaking, but I guess this is more of a fix that now has the same consequences as using toString with interpolate. |
If something other than a String is interpolated, the string is computed as follows: | ||
- If the object is not a `Number`, `Date` or `Array`, $interpolate looks for a custom `toString()` | ||
function on the object, and uses that. Custom means that | ||
`myObject.toString() !== `Object.prototype.toString()`. |
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.
This is not accurate and may confuse people. You mean myObject.toString !== Object.prototype.toString
.
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.
Thanks that's right
Both changes (to
@Narretz convinced me the current behavior (i.e. calling |
Updated again, should be good to go now |
- `undefined` and `null` are converted to `''` | ||
- if the value is an object that is not a `Number`, `Date` or `Array`, $interpolate looks for | ||
a custom `toString()` function on the object, and uses that. Custom means that | ||
`myObject.toString !== `Object.prototype.toString`. If that doesn't apply, `JSON.stringify` is used. |
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.
If that doesn't apply,
JSON.stringify
is used.
Nitpick: I would place that on it's own bullet.
LGTM BTW, in the commit message for the |
@gkalpak Okay, so ngBind previously set |
You have to special case them if you propose using a filter. But I think a filter is not necessary in this case (you can just call |
Except on Numbers, Dates and Arrays. Thanks to @danielkrainas for the initial implementation of this feature. This behavior is consistent with implementations found in other languages such as Ruby, Python, and CoffeeScript. http://rubymonk.com/learning/books/1-ruby-primer/chapters/5-strings/lessons/31-string-basics https://docs.python.org/2/library/stdtypes.html#string-formatting-operations http://coffeescriptcookbook.com/chapters/strings/interpolation The commit also exposes a private $$stringify method on the angular global, so that ngMessageFormat can use the same logic without duplicating it. Fixes angular#7317 Closes angular#8350 Fixes angular#11406 BREAKING CHANGE: When converting values to strings, interpolation now uses a custom toString() function on objects that are not Number, Array or Date (custom means that the `toString` function is not the same as `Object.prototype.toString`). Otherwise, interpolation uses JSON.stringify() as usual. Should you have a custom toString() function but still want the output of JSON.stringify(), migrate as shown in the following examples: Before: ```html <span>{{myObject}}</span> ``` After - use the `json` filter to stringify the object: ```html <span>{{myObject | json}}</span> ```
Fixes angular#11716 BREAKING CHANGE: `ngBind` now uses the same logic as $interpolate (i.e. {{myString}}) when binding, which means values other than strings are now transformed as following: - null / undefined become empty string - with an object's custom toString() function, except if the object is a Date, Array, or Number - otherwise with JSON.stringify Previously, ngBind would use always use toString(). The following examples show the different output: ```js $scope.myPlainObject = {a: 1, b: 2}; $scope.myCustomObject = {a: 1, b: 2, toString: function() {return 'a+b';}}; ``` Plain Object: ```html <!-- Before: --> <span ng-bind="myPlainObject">[object Object]</span> <!-- After: --> <span ng-bind="myPlainObject">{"a":1,"b":2}</span> ``` Object with custom toString(): ```html <!-- Before: --> <span ng-bind="myCustomObject">[object Object]</span> <!-- After: --> <span ng-bind="myCustomObject">a+b</span> ``` If you want the output of `toString()`, you can use it directly on the value in ngBind: ```html <span ng-bind="myObject.toString()">[object Object]</span> ```
Tests that - interpolated Date objects work for min/max - Date objects work for ng-min/ng-max
angularJs Version used: 1.7.4 What is the status of this PR, I believe it is merged into master branch... but I am getting he inconsistent results when interpolating the object, sometime getting full object some time getting for example
|
This is merged and included in all version since 1.6.0-rc.0 onwards. |
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)
ngBind has different binding logic for objects than interpolation
What is the new behavior (if this is a feature change)?
Same logic as interpolate
Does this PR introduce a breaking change?
Yes, the visible output is now different, i.e. consistent with interpolate. I don't think anyone relied on ngBind producing this output.
However, this PR is a step away from the proposal in #11406, which wants $interpolate to call toString() instead of toJson. See also PR #8350, and issue #7317
Please check if the PR fulfills these requirements
Other information:
Fixes #11716
BREAKING CHANGE:
ngBind
now uses the same logic as $interpolate (i.e. {{myString}}) whenbinding, which means objects are now transformed with JSON.stringify. Before, it
would use the object's toString() function.
The following example shows the different output:
Before:
After: