strictPropertyInitialization

Errors when a class field is declared but never assigned a value in the constructor.

Signature
"strictPropertyInitialization": boolean  // Default: true if strict; false otherwise
  • Added in TypeScript 2.7

Parameters

ParameterTypeDescription
$strictPropertyInitializationoptionalboolean= true if strict; false otherwiseWhen true, TypeScript raises an error when a class property is declared but not definitely assigned in the constructor. Requires strictNullChecks to have any effect.

Errors

Nothing at runtime; it reports error 2564, 'Property X has no initializer and is not definitely assigned in the constructor'.

Changelog

  • PHP 3.7The declare modifier on a class field lets you redeclare an inherited property's type without emitting a field, which also satisfies this check without emitting an assignment.
  • PHP 4.3useDefineForClassFields defaults to true for target ES2022 and above, which changes whether a declared-but-unassigned field exists as an own property at runtime.

A field declared name: string but never assigned holds undefined at runtime while its type says it cannot. strictPropertyInitialization closes that gap: every non-optional field must be given a value by the time the constructor returns.

Four ways to satisfy it

  • Assign it in the constructor, or use a parameter property (constructor(public name: string))
  • Give it an initialiser at the declaration site
  • Mark it optional (email?: string) or widen it to include undefined
  • Use the definite assignment assertion, email!: string, which tells the compiler something else assigns it

What trips people up

The check has no effect at all unless strictNullChecks is also on — enable it alone and it looks broken. It also only understands assignments made directly in the constructor body: extracting the work into this.init() defeats the analysis, and you are back to !.

Dependency-injection frameworks, ORMs and test fixtures assign fields from outside the constructor, which is what the definite assignment assertion exists for. Use it deliberately — like the non-null assertion, it silences the compiler rather than proving anything.

Example

Example · typescript
// "strict": true (so strictNullChecks and this flag are both on)
class UserAccount {
  name: string;                  // assigned in the constructor: OK
  accountType = "user";          // has an initialiser: OK
  email!: string;                // definite assignment assertion: OK
  address: string | undefined;   // type allows undefined: OK
  // phone: string;              // error 2564: no initializer

  constructor(name: string) {
    this.name = name;
  }
}

const u = new UserAccount("Heera");
console.log(u.name, u.accountType, u.email, u.address);
// -> Heera user undefined undefined

When to use it

  • Catching a constructor that forgets to set one of several fields after a refactor
  • Marking framework-injected fields with ! so the compiler stops asking
  • Deciding whether a field is really optional or just never initialised

Frequently asked questions

How do I fix 'Property has no initializer and is not definitely assigned in the constructor'?

Assign the property in the constructor, give it an initialiser, make it optional with ?, or add a definite assignment assertion (name!: string) if something outside the constructor sets it. Error 2564 comes from strictPropertyInitialization.

What does the exclamation mark after a class property mean?

It is the definite assignment assertion: it tells TypeScript the field will be assigned before it is read, so the initialisation check is skipped. It is erased at compile time and provides no runtime guarantee at all.

Why is strictPropertyInitialization not reporting anything?

It requires strictNullChecks. With null checks off, undefined is assignable to every type, so an unassigned field is not a type error and the flag has nothing to report.

More examples

What the assertion is hiding

Prints TypeError then "ran: select 1". The ! made tsc treat db as always present; the first call proves it was not. Use the assertion only where a framework really does assign the field before use.

Example · typescript
class Repo {
  db!: { query(sql: string): string }; // injected later

  find(id: string) {
    return this.db.query("select " + id);
  }
}

const r = new Repo();
try {
  r.find("1"); // db is still undefined
} catch (e) {
  console.log((e as TypeError).constructor.name); // TypeError
}

r.db = { query: (sql) => "ran: " + sql };
console.log(r.find("1")); // ran: select 1

Initialising via a helper defeats the check

Prints "untitled 100": the field really is assigned before anyone reads it. The compiler's analysis is intentionally shallow and stops at the constructor body, so assigning in a helper needs the assertion even though the code is correct.

Example · typescript
class Widget {
  // Error 2564: tsc does not follow assignments out of setup().
  // Written as `title!: string` it compiles.
  title!: string;
  width = 0;

  constructor() {
    this.setup();
  }

  setup() {
    this.title = "untitled";
    this.width = 100;
  }
}

console.log(new Widget().title, new Widget().width); // untitled 100

Discussion

  • Be the first to comment on this lesson.