readonly modifiers, and strict initialization rules.1. Class Field Declarations in TypeScript#
In plain JavaScript, you can dynamically assign properties to this inside a constructor without declaring them anywhere else.
In TypeScript, all instance properties must be declared in the class body before they can be assigned or accessed.
class DatabaseClient {
// 1. Explicit Field Declarations
host: string;
port: number;
readonly isCluster: boolean; // Readonly instance field!
// 2. Constructor
constructor(host: string, port: number, isCluster = false) {
this.host = host;
this.port = port;
this.isCluster = isCluster;
}
// 3. Instance Method
getConnectionUri(): string {
return `postgres://${this.host}:${this.port}`;
}
}
const client = new DatabaseClient("localhost", 5432);
console.log(client.getConnectionUri()); // "postgres://localhost:5432"
// ❌ Compiler Error: Cannot assign to 'isCluster' because it is a read-only property.
// client.isCluster = true;
2. strictPropertyInitialization Mechanics#
When "strict": true (or "strictPropertyInitialization": true) is enabled in tsconfig.json, TypeScript verifies that every declared class field is initialized either:
- Directly at its declaration site (inline initializer), OR
- Inside the
constructor()body.
If a field is declared but not initialized, TypeScript throws a compile-time error:
class ApiService {
// ❌ Compiler Error: Property 'endpoint' has no initializer
// and is not definitely assigned in the constructor.
endpoint: string;
constructor() {
// Oops! Forgot to assign this.endpoint = ...
}
}Why this rule exists:#
In JavaScript, an uninitialized property defaults to undefined at runtime. Calling .toUpperCase() on this.endpoint would crash the application with an unhandled TypeError.
3. 4 Strategies for Uninitialized Fields#
How do you handle class fields that cannot be assigned immediately inside the constructor?
Strategy 1: Inline Default Initializer (Preferred)#
Provide a default value right at the field declaration site:
class HTTPConfig {
timeoutMs: number = 5000; // Inferred as 'number', initialized automatically
headers: Record<string, string> = {};
}Strategy 2: Union with null or undefined#
If the field is intentionally empty upon instantiation:
class UserSession {
token: string | null = null; // Explicitly nullable
lastLogin: Date | undefined;
setToken(newToken: string) {
this.token = newToken;
}
}Strategy 3: Optional Property Modifier (?)#
Marks the field as optional (type becomes T | undefined):
class Order {
id: string;
couponCode?: string; // Type is string | undefined
constructor(id: string) {
this.id = id;
}
}Strategy 4: Definite Assignment Assertion Operator (!)#
If a property is initialized indirectly by a framework helper method (such as Angular @Input(), TypeORM @Column(), or a custom .init() setup function), you can append ! to tell TypeScript: “Trust me, this field will be assigned before it is ever read.”
class OrmUserEntity {
// The '!' suppresses strictPropertyInitialization check
id!: string;
email!: string;
// Framework calls this lifecycle hook after constructor execution
__initializeFromDatabase(row: { id: string; email: string }) {
this.id = row.id;
this.email = row.email;
}
}The Danger of !: The Definite Assignment Operator completely bypasses TypeScript’s safety net. If code reads entity.id before __initializeFromDatabase() runs, your app will crash at runtime with an unhandled TypeError.
4. Class Methods and this Binding#
Class methods in TypeScript are typed just like standard functions:
class Calculator {
value: number = 0;
add(amount: number): this {
this.value += amount;
return this; // Polymorphic 'this' for chaining!
}
// Arrow function property preserves 'this' context automatically!
reset = (): void => {
this.value = 0;
};
}
const calc = new Calculator();
calc.add(10).add(20);
console.log(calc.value); // 30
Summary & Next Steps#
In this episode:
- We learned that TypeScript requires declaring all instance fields in the class body.
- We analyzed
strictPropertyInitializationand why uninitialized fields throw errors. - We explored the 4 strategies for handling uninitialized properties (Default Initializers, Unions, Optional
?, and Definite Assertions!). - We used
readonlyclass fields for immutable property protection.
In Episode 17: Access Modifiers (public, private, protected), we will explore how to restrict class member visibility, and compare TypeScript’s compile-time modifiers with JavaScript’s native # private fields!

