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

Interpolation should call toString on objects #11406

Closed
ewinslow opened this issue Mar 23, 2015 · 14 comments
Closed

Interpolation should call toString on objects #11406

ewinslow opened this issue Mar 23, 2015 · 14 comments

Comments

@ewinslow
Copy link
Contributor

The string value is what is going to be used in the rendering of the information, but this doesn't seem to be respected.

http://plnkr.co/edit/ZLmiavC9lSlKj1b6cQJk?p=preview

For a real-world use-case: We'd like to return goog.Uri instances from our controller and have angular appropriately format them in the template, but it doesn't work -- it seems like the uri instances are compared by identity instead of string value (even though only string value will affect what the user sees), which I think was causing infinite loops. In the plunkr above I cannot get the right information to show up without manually stringifying first.

Right now our workaround is to call toString() or + "" manually within the controller, but this is just visual noise... it also is incentive to pass around strings instead of js objects, which I think would obscure the intent of the code.

@wesleycho
Copy link
Contributor

I don't think Angular should do that - you would get some weird behavior happening if it did that on objects. For example, here is some of the undesired behavior one might get if this was supported:

var foo = {};
foo.toString() // yields '[object Object]'
{} + '' // yields 0

These two examples would be strong arguments against supporting this IMO. I would rather Angular fail silently in those two cases.

For your case, you could always create a custom filter to format it as desired.

@ewinslow
Copy link
Contributor Author

Right now angular shows "{}" for the above example. That's not even weirder than calling toString()?

Current behavior is silly. Let's improve it. Throw a useful error, or call "toString", or concat with a string, but don't just show curlies or enter into an infinite loop...

@ewinslow
Copy link
Contributor Author

Also: '' + x gives a consistent result regardless of the type of x, so far as I can tell (i.e. calls toString()). The one rub is that for null, false, and undefined it gives "null", "false", and "undefined" respectively, when you might want that to be empty string instead. Those can be trivially special-cased if need be.

@lgalfaso
Copy link
Contributor

Hi, there is always a constant struggle between

  • Failing silently, and developers spending hours trying to figure out why something is not working
  • Failing every single time something is out of order, and end up very inflexible
  • Being too flexible by picking a behavior, and not making everybody happy as this will not be the behavior some are expecting

In most cases, the answer is a balance. We needs to be flexible and at the same time let developers know when there is something that is not supported/will not work. On top, semver rules should be followed for breaking changes.

With this in mind, I find the behavior to be acceptable. Now if the consensus is that calling toString, then this cannot happen in a minor release, and 1.5 would be the first opportunity.

@ewinslow
Copy link
Contributor Author

If this is a breaking change then semver means it has to wait until 2.0. :(

@ewinslow
Copy link
Contributor Author

But thanks for getting back. I can definitely appreciate the bind you guys are in and it's easy to think that something is the right way just because it makes sense to me, but that is obviously not always the case.

@yordis
Copy link

yordis commented Mar 24, 2015

👍 This should be the expected behavior. @wesleycho if you dont want to see '[object Object]' I prefer customize the toString function rather than create a filter. Your suggestion is create a filter per every single different output, that's more work and more code so more bugs 😄

@wesleycho
Copy link
Contributor

@yordis a developer should not have to monkeypatch toString - that is a bad workaround. In this case, I absolutely prefer an api where the developer makes explicit how he/she wants it to be displayed.

@yordis
Copy link

yordis commented Mar 24, 2015

@wesleycho Is that monkeypath? Why? For me it's prototype inheritance.

If you know the object have to output something different than the normal output, you just override the function. _Why don't use the flexibility of the language_, developers need to know what they are doing. If you are a javascript developer and you know how the toString is used it in Javascript there is not problem.

Every object has a toString() method that is automatically called when the object is to be represented as a text value or when an object is referred to in a manner in which a string is expected. By default, the toString() method is inherited by every object descended from Object. If this method is not overridden in a custom object, toString() returns "[object type]", where type is the object type." MDN

What's wrong with this.

function Person (firstname, lastname) {
  this.firstname = firstname;
  this.lastname = lastname;
}
Person.prototype.toString = function() {
  return this.firstname + ' ' + this.lastname;
}

var me = new Person('Yordis', 'Prieto');

Person.toString() //function Person (firstname, lastname) { this.firstname = firstname; this.lastname = lastname; }
me.toString() // "Yordis Prieto"
function you() {}
var fo = new you()

you.toString() // "function you() {}"
fo.toString() // "[object Object]"

I am programming in Ruby using RoR and you see a lot of Ruby extension from RoR making the language even better. I said this because we put too many limitations to Javascript making us full depended of browser providers. I am telling you that because the monkeypatch thing call my attention. The prototype inheritance is there, use it!

@tonte-pouncil
Copy link

I think it should call the toString() method. If the $parse service doesn't call toString() method, what does it call?

@sthames42
Copy link

This came up for me the other day and I can get around it. But since the issue has been placed in the Ice Box, I must point out that stringification/interpolation of numbers and strings does not produce quoted strings which means strings at least are not being JSONified. Furthermore, as has been pointed out, ng-bind is not consistent with interpolation markup just as interpolation markup is not consistent with Javascript itself.

All objective languages I've worked with, such as C#, Java, Perl, PHP, and Javascript, provide a method called to return the string representation of the object when that object is stringified/interpolated. AngularJS is stepping outside this widely used standard for reasons I cannot determine.

As the 2.0 release is fast approaching, this issue should be addressed before more problems with Angular's stringification/interpolation inconsistency with industry standard are discovered. Since this appears to be a known problem such that everyone is already having to use filters to overcome the default stringification of interpolated objects as serialized JSON, It may not be such a breaking change.

@gkalpak
Copy link
Member

gkalpak commented Feb 16, 2016

AngularJS is stepping outside this widely used standard for reasons I cannot determine.

The reason it that [object Object] is not a very useful representation of an object. Since $interpolation is basically for strings and primitives, you should not pass objects to it anyway. I'm pretty sure C#, Java, Perl, PHP would through an error if you tried to pass objects to their string-targetted methods/functions.
JavaScript (and even more so Angular) is know for its "forgiving-mess", so JSON.stringify() is used instead, as it usually provides a better representation of an object than [object Object].

Admittedly, it doesn't work so well for Date, because (contrary to most built-in objects) it does provide a useful .toString() method (but then again you are not expected to pass a Date object as a parameter in an interpolated string.
If you would like to submit a PR to handle Date objects specially inside $interpolate, I would be happy to take a look 😃

@sthames42
Copy link

Thank you for responding, @gkalpak. No, there is no need to do anything on my part. Like everyone else, I'm using a filter to render the stringified date.

The default output of JSON.stringify() (where an object has not provided a toJSON() method), is to return a string of the object serialized as JSON. I find it hard to believe this is any more useful than "[object Object]" for the purposes of interpolation.

ng-bind renders an object by calling the toString() method, not toJSON() or JSON.stringify(). Given your comment that "[object Object]" is not a useful value, why is it okay with ng-bind and not with interpolation markup?

Given the prolific nature of date values in web based applications, I would expect a Date object to be used in interpolation quite often. At least, we certainly use it often and it is much easier than moving strings around especially if you might want to do some date arithmetic. I would assume Date objects are used in interpolation more than you might think and everyone else is using filters like I am.

Having worked with all the languages you mention quite extensively, I know that both C# and Java, being strongly typed languages, may error when an Object is passed where a String is expected. There are some cases where an object can be provided where a string is expected but the Object.toString() method is called to render the object as a string. Java, PHP, and Perl all provide object stringification methods as toString(), __toString(), and stringify(), respectively. These methods are called anytime an object is stringified/interpolated.

I did not open this issue and probably would not have given I don't make a habit of telling people how to code unless they work for me. But I strongly recommend this issue is moved out of the IceBox and considered before 2.0 is released. Automatic stringification of Javascript objects provides great flexibility and is blocked by a feature of AngularJS that has little value to the end user.

Narretz added a commit to Narretz/angular.js that referenced this issue Jun 24, 2016
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>
```
@shmdhussain
Copy link

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 [object Object]

for example

{{ {id:123}  }} //sometime outputs '{id:123}'
{{ {id:123}  }} //sometime outputs [object Object]

Originally posted by @shmdhussain in #14715 (comment)

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

No branches or pull requests

9 participants