21.5 strictPropertyInitialization: Class Fields Must Be Assigned
Right, so you’ve turned on strictNullChecks. Good for you. You’re no longer living in the wild west where undefined is a valid state for everything. But now you’re staring at a class like this, and TypeScript is yelling at you. Let’s talk about why.
class UserAccount {
name: string; // 💥 Error: Property 'name' has no initializer and is not definitely assigned in the constructor.
isAdmin: boolean;
}
TypeScript’s strictPropertyInitialization rule is the hall monitor of your class properties. It demands that every instance property that doesn’t have undefined as part of its type must be assigned a value by the time the constructor function finishes. It’s not being pedantic for fun; it’s trying to save you from a runtime undefined error that would make you facepalm. Think of it this way: if the constructor doesn’t set it, and you haven’t promised it might be undefined, how on earth is it supposed to get a value?
The Two Ways to Placate the Compiler
You have two main, legitimate paths to satisfaction here. The first is the obvious one: just assign the value in the constructor.
class UserAccount {
name: string;
isAdmin: boolean;
constructor() {
this.name = "New User";
this.isAdmin = false;
}
}
Boom. Error gone. The compiler can trace the execution path of the constructor and see that by the time new UserAccount() returns, both fields have a value. This is the most straightforward and usually the best approach.
The second path is to use the definite assignment assertion operator, which is the exclamation mark (!). This is you telling the type checker, “I, a brilliant and infallible developer, promise you that this field will be assigned, even if you’re too myopic to see it. Now stop bothering me.”
class UserAccount {
name!: string; // I pinky-swear I'll set this later
isAdmin: boolean;
constructor() {
// I might set name via some other method later...
this.initializeName();
this.isAdmin = false;
}
private initializeName() {
this.name = "New User";
}
}
Use this power sparingly and wisely. It’s a great way to shoot yourself in the foot if you break your promise. It’s perfect for dependency injection frameworks or other scenarios where a framework is wiring things up for you after the constructor runs, but for your own code, prefer the constructor.
When You’re Actually Dealing with Optional Fields
Sometimes, a field genuinely might start its life as undefined. Maybe you’re fetching its value asynchronously after the object is constructed. In this case, the correct fix isn’t to lie with !, but to tell the truth by updating the type to include undefined.
class AsyncUserProfile {
profileData: { avatar: string; bio: string } | undefined; // This is honest
constructor() {
// No assignment here is fine now.
}
async loadData() {
this.profileData = await fetchSomeData();
}
}
Now, the type system forces you to check for undefined every time you access profileData, which is exactly what you should be doing. It turns a potential runtime error into a compile-time check. This isn’t a workaround; it’s you modeling your program’s actual behavior accurately.
The Constructor Parameter Properties Shortcut
If you find yourself writing a constructor that just takes arguments and assigns them directly to same-named properties, TypeScript has a fantastic bit of syntactic sugar for you: Parameter Properties.
// Instead of this verbose nonsense...
class VerboseUser {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
// ...do this elegant thing.
class ConciseUser {
constructor(public name: string, public id: number) {}
}
By adding a visibility modifier (public, private, readonly) to the constructor parameter, TypeScript automatically declares and initializes the class property for you. It completely sidesteps the strictPropertyInitialization error because the assignment is implicit. This is one of those language features that feels like magic in the best way possible.
The One Weird Trick with --useDefineForClassFields
Here’s a fun edge case that might bite you if you’re using modern ECMAScript features. The useDefineForClassFields compiler flag changes how class fields are emitted to JavaScript. This can interact strangely with the definite assignment assertion.
The gist is: with that flag on, a field like name!: string might actually be initialized to undefined at runtime before the constructor runs, even though the type system says it can’t be. This is a rare but nasty disconnect between the type world and the runtime world. It’s a reminder that ! is a type system assertion, not a runtime guarantee. For 99% of projects, you won’t hit this, but it’s good to know the sharp edges exist.