This TypeScript error occurs when strictPropertyInitialization is enabled and class properties are declared but not initialized. The fix involves initializing properties in the constructor, using definite assignment assertions, or adjusting compiler options.
The TypeScript error "Property 'x' is declared in class 'X' but its initializer or other property definition is missing" (TS2564) occurs when you have the `strictPropertyInitialization` compiler option enabled (or `strict` mode which includes it) and you declare a class property without providing an initial value. This TypeScript safety feature ensures that class properties are properly initialized before use, preventing runtime errors where you might try to access `undefined` values. TypeScript needs to know that properties will have values when the class instance is created. The error appears because TypeScript cannot guarantee that the property will be assigned a value before it's accessed. This could lead to runtime errors in JavaScript where the property would be `undefined`. Common scenarios include: 1. Properties declared without initial values 2. Properties assigned only conditionally in the constructor 3. Properties that are meant to be initialized later via methods 4. Properties with complex initialization logic
The simplest fix is to provide an initial value when declaring the property:
// BEFORE - Error
class User {
name: string; // Error: Property 'name' has no initializer
}
// AFTER - Fixed with initial value
class User {
name: string = ''; // Initialize with empty string
age: number = 0; // Initialize with 0
isActive: boolean = false; // Initialize with false
tags: string[] = []; // Initialize with empty array
}
// For complex types, use appropriate defaults
class Config {
settings: Record<string, any> = {};
timeout: number = 5000;
retries: number = 3;
}This ensures the property always has a value when the class is instantiated.
If the initial value depends on constructor parameters or needs computation, initialize in the constructor:
// BEFORE - Error
class User {
name: string;
age: number;
constructor(firstName: string, lastName: string, birthYear: number) {
// Error: Properties not initialized
}
}
// AFTER - Fixed in constructor
class User {
name: string;
age: number;
constructor(firstName: string, lastName: string, birthYear: number) {
this.name = `${firstName} ${lastName}`;
this.age = new Date().getFullYear() - birthYear;
}
}
// For optional parameters with defaults
class Product {
name: string;
price: number;
discount: number;
constructor(name: string, price: number, discount?: number) {
this.name = name;
this.price = price;
this.discount = discount ?? 0; // Use nullish coalescing for default
}
}Make sure all code paths in the constructor assign values to all properties.
When you know a property will be initialized (but TypeScript can't verify it), use the definite assignment assertion (!):
// Use ! when you guarantee initialization
class Database {
connection!: Connection; // ! tells TypeScript "trust me, this will be initialized"
async initialize() {
this.connection = await createConnection();
}
}
// For properties set by frameworks or dependency injection
class Controller {
service!: Service; // Injected by framework
// Framework will set this property before use
}
// For lazy initialization
class Cache {
data!: Map<string, any>;
getData(): Map<string, any> {
if (!this.data) {
this.data = new Map();
}
return this.data;
}
}
// WARNING: Only use ! when you're certain the property will be initialized
// before any access. Misuse can lead to runtime errors.The ! operator tells TypeScript to skip the initialization check for that property.
If a property can legitimately be undefined, mark it as optional:
// Use ? for properties that may be undefined
class UserProfile {
name: string;
avatarUrl?: string; // Optional - may be undefined
bio?: string; // Optional - may be undefined
constructor(name: string) {
this.name = name;
// avatarUrl and bio can remain undefined
}
}
// Access optional properties safely
const profile = new UserProfile('Alice');
console.log(profile.avatarUrl?.toUpperCase()); // Optional chaining
console.log(profile.bio ?? 'No bio provided'); // Nullish coalescing
// Interface with optional properties
interface Config {
requiredSetting: string;
optionalSetting?: number;
anotherOptional?: boolean;
}
// Class implementing interface
class AppConfig implements Config {
requiredSetting: string;
optionalSetting?: number;
constructor(required: string) {
this.requiredSetting = required;
}
}Optional properties (? syntax) explicitly tell TypeScript that undefined is a valid value.
If you need to disable this check (not recommended for new code), adjust your tsconfig.json:
// OPTION 1: Disable strictPropertyInitialization only
{
"compilerOptions": {
"strict": true,
"strictPropertyInitialization": false // Disable just this check
}
}
// OPTION 2: Disable all strict checks (not recommended)
{
"compilerOptions": {
"strict": false // Disables all strict checks including property initialization
}
}
// OPTION 3: Use overrides for specific files (TypeScript 4.1+)
{
"compilerOptions": {
"strict": true
},
"files": [
"src/strict-code.ts"
],
"include": [
"src/**/*"
],
"exclude": [
"src/legacy/**/*" // Legacy code without strict checks
]
}Recommendation: Instead of disabling the check, fix the initialization issues. The check exists to prevent runtime errors.
If you have legacy code that can't be easily fixed, consider:
1. Creating a separate tsconfig for legacy code
2. Using // @ts-ignore comments sparingly
3. Gradually fixing properties over time
When properties are initialized conditionally, ensure all code paths assign values:
// BEFORE - Error: Property may be undefined
class Auth {
token: string;
constructor(useToken: boolean) {
if (useToken) {
this.token = 'generated-token';
}
// Error: token not assigned when useToken is false
}
}
// AFTER 1: Assign in all branches
class Auth {
token: string;
constructor(useToken: boolean) {
if (useToken) {
this.token = 'generated-token';
} else {
this.token = ''; // Assign default in else branch
}
}
}
// AFTER 2: Use ternary operator
class Auth {
token: string;
constructor(useToken: boolean) {
this.token = useToken ? 'generated-token' : '';
}
}
// AFTER 3: Initialize with default, then conditionally update
class Auth {
token: string = ''; // Initialize with default
constructor(useToken: boolean) {
if (useToken) {
this.token = 'generated-token';
}
}
}
// AFTER 4: Make property optional if it can be undefined
class Auth {
token?: string; // Mark as optional
constructor(useToken: boolean) {
if (useToken) {
this.token = 'generated-token';
}
}
}TypeScript analyzes all possible code paths in the constructor to ensure properties are initialized.
### Understanding strictPropertyInitialization
The strictPropertyInitialization compiler option (enabled by strict: true) requires that:
1. Non-optional instance properties must be initialized:
- In their declaration: name: string = "default";
- In the constructor
- Via a definite assignment assertion (!)
2. Optional properties (?) are exempt from this requirement
3. Static properties are NOT checked by this rule
### Definite Assignment Assertions vs Non-null Assertions
Both use ! syntax but have different meanings:
// Definite assignment assertion - "I promise this will be initialized"
class Example1 {
value!: string; // TypeScript won't check initialization
}
// Non-null assertion - "This value won't be null/undefined"
class Example2 {
getValue(): string | null {
return Math.random() > 0.5 ? 'hello' : null;
}
useValue() {
const val = this.getValue();
console.log(val!.toUpperCase()); // ! says "val won't be null here"
}
}### Abstract Classes and Inheritance
Abstract classes have special rules:
abstract class Base {
abstract requiredProp: string; // Abstract - must be implemented
optionalProp?: number; // Optional in base class
initializedProp: string = 'default'; // Has initializer
}
class Derived extends Base {
requiredProp: string; // Error: No initializer
constructor() {
super();
this.requiredProp = 'value'; // Must initialize in constructor
}
}### Property Initialization Order
Properties are initialized in this order:
1. Property initializers (e.g., name: string = "default")
2. Constructor execution
3. Definite assignment assertions bypass the check entirely
### Framework-Specific Patterns
Angular/React/Vue components often use definite assignment assertions:
// Angular - properties injected by DI
@Component({...})
export class MyComponent {
@Input() inputValue!: string; // Set by parent component
@ViewChild(ChildComp) child!: ChildComp; // Set by framework
ngOnInit() {
// Safe to use inputValue and child here
}
}
// React Class Component
class MyComponent extends React.Component<Props> {
private timerId!: number; // Set by setTimeout
componentDidMount() {
this.timerId = window.setTimeout(() => {}, 1000);
}
componentWillUnmount() {
window.clearTimeout(this.timerId);
}
}### Migration Strategy for Legacy Code
When enabling strictPropertyInitialization in an existing codebase:
1. Start with `strictPropertyInitialization: false`
2. Enable for new files only using file-specific tsconfig overrides
3. Gradually fix properties starting with critical classes
4. Use a linter rule to track progress: "@typescript-eslint/strictPropertyInitialization": "error"
### Performance Considerations
Property initialization checks happen at compile time with no runtime cost. The ! operator also has no runtime effect - it's purely a TypeScript compile-time directive.
### Common Pitfalls
1. Forgotten else branches in conditional initialization
2. Async initialization that happens after constructor returns
3. Circular dependencies where Class A needs Class B which needs Class A
4. Framework lifecycle where properties are set after constructor but before use
For async initialization patterns:
class AsyncExample {
private dataPromise: Promise<Data>;
data!: Data; // Will be set by the promise
constructor() {
this.dataPromise = this.loadData();
this.dataPromise.then(result => {
this.data = result;
});
}
// Alternative: Don't store as property, use async method
async getData(): Promise<Data> {
return this.dataPromise;
}
}Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript