TypeScript: Javascript: Object.assign to assign property values for classes is not respected
Perhaps TS should be able to identify Object.assign(this, ...) then infer the properties that are assigned to this? Or is this not possible to do? I am not sure.
TypeScript Version:
Version 3.0.3
Search Terms:
- object assign
- class
- does not exist on type
Code A.js
export default class A {
constructor ({ x, y, z }) {
Object.assign(this, {x, y, z});
}
f () {
return this.x;
}
}
Expected behavior:
TS should be able to identify x as a property of this.
Actual behavior: Throwing:
Property 'x' does not exist on type 'A'.
Related Issues: no
Side Note: I am using TS with Javascript because YouCompleteMe switched to using TSServer for semantic completion. I absolutely love TS so was really happy about the switch! Thanks for the great work!
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 69
- Comments: 24 (3 by maintainers)
Commits related to this issue
- Run 50 more features before the whole page has loaded (#6357) — committed to refined-github/refined-github by fregante a year ago
Came here from #28883. I understand that in the general case, the type of the second argument to
Object.assigncould be of typeany, but in my specific case I define that type via an interface beforehand:It seems to me that comparison between the types of
thisandconfigcould be done, and ifconfigis some type that’s not assignable tothis, then that would be the condition where you couldn’t determine the type.The alternative right now is to manually assign every property of the config interface to the corresponding class property, which works but is a pain:
(This whole thing gets even more ugly when dealing when using optional properties on the config object to allow the use of default values:
and as you can see, with lots of properties on the config object, you quickly wind up with a large chunk of code that just does the same job
Object.assignnormally does, as it automatically handles optional properties. However, becauseObject.assigninterprets a key with valueundefinedas present, this would be blocked on #13195 for full compatibility.)Is there any way to achieve this right now? If not, is it viable to implement? I could look into putting a PR together, but no promises as I’ve never worked with TS’s source before.
Why not support return different thing from constructor()?
It’s more common than only Object.assign.
Not quite, though… The issue was never that the arguments were improperly typed, but that the
Object.assigncall doesn’t update which properties are present on its first argument, in this case the constructed instance.Maybe a better example:
I came across a similar issue where I wanted to essentially extend a type from a third-party library, but didn’t want to manually write out all of the properties (lazy 😅). This is what I ended up doing:
Create class from a generic type:
Create a base class for my
Persontype:Extending
PersonClassis also very easy:Here are some more example use cases:
Check it out on the TypeScript playground!
What I ended up doing in my case was to merge/augment the class with an interface like below. This is not a solution to the problem but a workaround.
The idea is to have an interface that defines the object passed to the constructor function that has the same name as the class.
If you have a base abstract class that you want to use together with generics to dynamically defines class properties, you can do this:
Then the abstract class can be used like this:
I’ve been looking for this for a while and your solution @asleepace is perfectly doing the job!
By directly calling the
createClassFromTypemethod when declaring the actual class most of the boilerplate is avoided:@PLSFIX This helps somewhat for users of the class, but if class methods use any of the properties of
Optsthey would still have to be declared as fields, and to have them asreadonlythe compiler would still complain that they are “definitely not assigned in the constructor”. So your solution obviously doesn’t help making those properties fields.If we could have field designators (public/private/protected + readonly) in destructured object parameters this problem would be solved AND we would have the added bonus of being able to initialize parts of an object automatically without having to use
Object.assignat all:This would be amazing. Right now, if you want to augment a POJO with some methods, say, for computing related values, it’s really easy to do in JS — but basically impossible to type in TS without a lot of boilerplate or circular reference issues:
If we want to guarantee that the new methods (
aandb) won’t be shadowed by an own prop of the pojo, we could do something in vanilla JS like:But trying to type the
thisfor the functions on themethodsobjects (which should be{ x: string } & typeof methods) blows up with circularity errors that are only avoided when using a class.The best hack I’ve come up with is:
Just found this article which outlines a way to accomplish this with the return value of
Object.assign(), but doesn’t address performing the in-place operation/modifying the type of the first argument without reassignment. https://spin.atomicobject.com/2018/05/14/type-safe-object-merging-2-8/I’m interested in making a PR for this behavior; does anyone more familiar with the project have any pointers on possible starting points?
Actually, it might not be possible to support this feature for ambiguous objects.
E.g.
Since obviously
obj1can be of any type in JS; hence there is no way to find out what is being assigned tothis.But I think it would be a good idea to be able to support explicit assignment to
thiswithObject.assign.Any solution except TypeScript itself handling this will be an ugly workaround. Using a class declaration wrapper function every time we want to declare a class is just hideous. Yes, it works, but so does resorting to
anyor going other pointless routes that don’t really address the problem.A workaround solution I used was to create a type alias for the base class. Something like:
At least you get intellisense tips and typechecking
You could get around that with a definite assignment assertion (
!), but it would be nice if TypeScript could figure that out instead.