I have a User class that has a decorated property "name". I am trying to initialize this property in parent class (Base) constructor using Object.assign but the value is "undefined". When I remove the decorator then the property is initialized with a correct value. See code below - I use typescript version 5.4.2.
Could anyone help me out please? Is there any way how to make this work?
Thanks.
// test.ts
(Symbol as any).metadata ??= Symbol("Symbol.metadata") // Polyfill
function Value(value: string) {
return (_target: any, context: ClassFieldDecoratorContext) => {
context.metadata![context.name] = value
}
}
class Base {
constructor(data: any) {
Object.assign(this, data)
}
}
interface IUser {
id?: string
name?: string
}
class User extends Base implements IUser {
id?: string
constructor(data: IUser) {
super(data)
}
@Value("test")
name?: string
}
const data = {
id: "1",
name: "John Doe"
}
const user = new User(data)
// user.id is 1
// user.name is undefined
This is considered a bug in TypeScript, reported at microsoft/TypeScript#56000.
This is a bit of fallout from the fact that public class fields as implemented in modern JavaScript differs semantically from how TypeScript originally implemented them. In particular, if you declare a class field in a JavaScript subclass, that field will be initialized as undefined
and not automatically inherited from the superclass. If you want TypeScript to behave to conform with JavaScript, you need to enable the --useDefineForClassField
flag. And you probably ultimately do want to do that, since that's how things work going forward.
Additionally, we know that decorators are different from how TypeScript originally thought they would be (notice a theme here?) so there's a new implementation in TypeScript 5.0 and above.
It looks like the downleveling for JavaScript decorators in TypeScript always acts as if --useDefineForClassField
is enabled. So even when you have the flag off, decorated fields will suddenly act like the flag is on, giving you the undefined
behavior you're seeing here.
It's a bug and probably will be fixed, but... ultimately if you're using JS decorators you should plan to use JS class fields as well, because as soon as you start targeting a JS runtime that implements JS decorators, they will also implement JS class fields. Any attempt to maintain new decorators with old fields is probably going to eventually break.