Definite Assignment Assertions
Promise the compiler a variable or field really does get assigned, just not where it can see.
Under strictNullChecks (and strictPropertyInitialization), TypeScript insists that class fields and variables are assigned before use. Usually that is exactly right. But sometimes assignment happens somewhere the compiler cannot follow β a DI framework, a lifecycle hook, a test beforeEach, a late setup call.
The definite assignment assertion is a lone ! after the name, meaning "this will be assigned, take my word for it."
class Widget {
private el!: HTMLElement; // assigned later in init(), not the constructor
init(root: HTMLElement) {
this.el = root;
}
}
let ready!: boolean; // assigned before first use elsewhereLike the non-null !, it is a compile-time promise with zero runtime effect. Use it only when you genuinely control the initialization order.
Example
When to use it
- A class initialises a database connection in a separate init() method rather than the constructor, requiring ! on the field.
- A module-level variable is populated by a before-hook in a test framework, needing ! to silence the uninitialised error.
- A lazy-loaded resource field uses ! because the DI container guarantees injection before any method is called.
More examples
Definite assignment on class field
The ! assertion tells TypeScript that connection will be assigned before query() is called, even though the constructor does not set it.
class DataService {
private connection!: DbConnection; // initialised in init(), not constructor
async init(): Promise<void> {
this.connection = await DbConnection.open();
}
async query(sql: string): Promise<unknown[]> {
return this.connection.execute(sql);
}
}
interface DbConnection { execute(sql: string): Promise<unknown[]>; open?(): void; }
declare namespace DbConnection { function open(): Promise<DbConnection>; }Definite assignment on a variable
Uses ! on a module-level variable to suppress the 'used before assignment' error when a setup function fills it in.
let config!: { host: string; port: number };
function loadConfig(): void {
config = { host: 'localhost', port: 5432 };
}
loadConfig();
console.log(config.host); // safe at runtimeWithout ! β the compiler error
Contrasts the commented-out field (which errors) with the ! version to clarify exactly when the assertion is needed.
class Repo {
// db: Database; // Error: Property 'db' has no initializer
db!: Database; // OK with definite assignment assertion
constructor(private setup: () => Database) {
this.db = setup();
}
}
declare class Database { query(sql: string): unknown[]; }
Discussion