Question
In an Angular component, I have a property like makes: any[];, and TypeScript shows this error:
Property 'makes' has no initializer and is not definitely assigned in the constructor.
Here is the component:
import { MakeService } from './../../services/make.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-vehicle-form',
templateUrl: './vehicle-form.component.html',
styleUrls: ['./vehicle-form.component.css']
})
export class VehicleFormComponent implements OnInit {
makes: any[];
vehicle = {};
constructor(private makeService: MakeService) {}
ngOnInit() {
this.makeService.getMakes().subscribe(makes => {
this.makes = makes;
console.log('MAKES', this.makes);
});
}
onMakeChange() {
console.log('VEHICLE', this.vehicle);
}
}
Why does this error happen, and what is the correct way to handle the makes property in Angular and TypeScript?
Short Answer
By the end of this page, you will understand why TypeScript complains about uninitialized class properties, how Angular lifecycle methods affect property assignment, and the safest ways to fix the issue using default values, optional properties, or definite assignment.
Concept
TypeScript can be configured to use strict property initialization. When this rule is enabled, every class property must be initialized in one of these ways:
- given a value where it is declared
- assigned inside the constructor
- marked as optional
- marked with the definite assignment assertion (
!)
In your component, this property is declared but not initialized:
makes: any[];
TypeScript sees that:
makesis expected to hold an array- no initial value is provided
- the constructor does not assign it
ngOnInit()runs later, not during construction
So TypeScript warns that the property might still be undefined when accessed.
This matters because TypeScript is trying to protect you from runtime bugs. For example, if your template or component code tries to use this.makes.length before the HTTP request completes, the app could fail.
In Angular, this happens often with data loaded asynchronously from services. A request starts in ngOnInit(), but the property is empty until the response arrives. That means you should model that state clearly in your code.
The most common beginner-friendly fix is to initialize the property with an empty array:
: [] = [];
Mental Model
Think of a class property like a labeled storage box.
If you write:
makes: any[];
you are saying, "This box should contain an array," but you never actually put anything in it at the start.
TypeScript checks the class and says, "What if someone opens the box before anything is placed inside?"
In Angular, ngOnInit() is like a worker who fills the box later. But the box already exists before that worker arrives. So TypeScript wants you to either:
- put in a default value now:
[] - clearly say the box may be empty:
makes?: any[] - promise that it will be filled before use:
makes!: any[]
For arrays used in templates, starting with an empty array is usually the safest choice.
Syntax and Examples
Common fixes
1. Best default for arrays: initialize immediately
makes: any[] = [];
Full example:
import { Component, OnInit } from '@angular/core';
import { MakeService } from './../../services/make.service';
@Component({
selector: 'app-vehicle-form',
templateUrl: './vehicle-form.component.html',
styleUrls: ['./vehicle-form.component.css']
})
export class VehicleFormComponent implements OnInit {
makes: any[] = [];
vehicle = {};
constructor(private makeService: MakeService) {}
ngOnInit(): void {
this.makeService.getMakes().subscribe( {
. = makes;
});
}
}
Step by Step Execution
Consider this version:
export class VehicleFormComponent implements OnInit {
makes: string[] = [];
constructor(private makeService: MakeService) {}
ngOnInit(): void {
this.makeService.getMakes().subscribe(makes => {
this.makes = makes;
});
}
}
Here is what happens step by step:
- The component instance is created.
makesis immediately set to[].- The constructor runs.
- Angular calls
ngOnInit(). getMakes()starts an asynchronous operation.- Until the response arrives,
makesis still[]. - When the data arrives, the
subscribecallback runs.
Real World Use Cases
This pattern appears often in Angular and TypeScript apps:
- Dropdown data: loading makes, categories, countries, or roles from an API
- Table data: starting with
[]before records are fetched - Form options: select menus that depend on async service calls
- Dashboard widgets: charts and summaries loaded after component initialization
- Search results: empty list first, real results later
Example: product categories for a form
categories: Category[] = [];
ngOnInit(): void {
this.categoryService.getAll().subscribe(data => {
this.categories = data;
});
}
Using [] is practical because the template can render immediately, even if the data is not loaded yet.
Real Codebase Usage
In real projects, developers usually combine strict typing with safe defaults.
Common pattern: initialize collections
users: User[] = [];
errors: string[] = [];
selectedIds: number[] = [];
This avoids repeated null checks.
Common pattern: initialize objects when possible
vehicle: Vehicle = {
id: 0,
makeId: 0,
model: ''
};
Common pattern: use interfaces instead of any
interface Make {
id: number;
name: string;
}
makes: Make[] = [];
Common pattern: handle async errors
ngOnInit(): void {
this..().({
: {
. = makes;
},
: {
.(, err);
}
});
}
Common Mistakes
1. Declaring a property without initializing it
Broken code:
makes: any[];
Why it is a problem:
- strict TypeScript sees it may be
undefined - async assignment in
ngOnInit()happens later
Better:
makes: any[] = [];
2. Using any[] everywhere
Broken code:
makes: any[] = [];
This works, but loses type safety.
Better:
interface Make {
id: number;
name: string;
}
makes: Make[] = [];
3. Using ! just to silence the error
Code:
Comparisons
| Approach | Example | When to use | Pros | Cons |
|---|---|---|---|---|
| Initialize with default value | makes: Make[] = [] | Best for arrays and lists | Safe, simple, template-friendly | Empty array is not the same as "not loaded" |
| Optional property | makes?: Make[] | When property may truly be missing | Honest data model | Requires undefined checks |
| Definite assignment assertion | makes!: Make[] | When framework assigns it later and you are certain | Removes error quickly | Less safe, can hide bugs |
| Assign in constructor | constructor() { this.makes = []; } |
Cheat Sheet
// Best fix for array properties
makes: Make[] = [];
// Optional property
makes?: Make[];
// Definite assignment assertion
makes!: Make[];
// Constructor assignment
constructor() {
this.makes = [];
}
Quick rules:
- If a property should always be an array, initialize it with
[] - If a property may be missing, use
? - If you use
!, be sure it really will be assigned before use ngOnInit()happens after construction, so it does not satisfy strict property initialization- Prefer real interfaces over
any
Recommended Angular version of your code:
interface Make {
id: number;
name: string;
}
makes: Make[] = [];
Useful reminder:
FAQ
Why does TypeScript show this error in Angular?
Because strict property initialization is enabled, and the property is not initialized where declared or in the constructor.
Why doesn't assigning in ngOnInit() fix the error?
Because ngOnInit() runs after the object is created. TypeScript checks initialization during construction time.
What is the best fix for an array property?
Usually this:
makes: Make[] = [];
Should I use ! to remove the error?
Only if you are certain the property will be assigned before any use. For arrays loaded from APIs, a default empty array is usually safer.
Is any[] = [] acceptable?
It works, but using a proper type like Make[] = [] is better for safety and readability.
Can I disable strict property initialization?
Yes, in TypeScript config, but that usually reduces safety. It is better to fix the property correctly.
What if I need to know whether data has loaded yet?
Use a separate loading flag such as isLoading = true instead of relying only on whether the array is empty.
Mini Project
Description
Build a small Angular component that loads a list of car makes from a service and displays them safely. This project demonstrates how to initialize async data correctly, avoid strict property initialization errors, and use stronger typing instead of any.
Goal
Create a component that fetches and displays a typed list of vehicle makes without triggering the "has no initializer" TypeScript error.
Requirements
- Create a
Makeinterface with at leastidandnamefields. - Declare the
makesproperty using theMake[]type and initialize it safely. - Load data from a service in
ngOnInit(). - Render the list in the template using
*ngFor. - Show a loading message before data arrives.
Keep learning
Related questions
Angular formGroup Error Explained: Fixing 'Can't bind to formGroup' in Reactive Forms
Learn why Angular shows 'Can't bind to formGroup' and how to fix it by importing ReactiveFormsModule correctly.
Fix "Element implicitly has an 'any' type" in TypeScript Object Indexing
Learn why TypeScript rejects string object indexing and how to fix it with keyof, unions, and typed object keys in React.
Fix 'Could not find a declaration file for module' in TypeScript
Learn why TypeScript cannot find declaration files for a package and how to fix it with types, package.json, and module resolution.