Question

Fix "Property has no initializer" in Angular TypeScript Components

typescriptangular

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:

  • makes is 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:

makes: any[] = [];

This works well because before the server response arrives, makes is still a valid 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.

Take Quiz

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 => {
      this.makes = makes;
    });
  }
}

Why this is good:

  • makes always contains an array
  • template code can safely loop over it
  • no undefined checks are needed for basic array usage

2. Mark the property as optional

makes?: any[];

This means makes may be undefined.

Example:

makes?: any[];

ngOnInit(): void {
  this.makeService.getMakes().subscribe(makes => {
    this.makes = makes;
  });
}

If you do this, you must handle the undefined case when reading it.

3. Use definite assignment assertion

makes!: any[];

This tells TypeScript: "Trust me, this property will be assigned before it is used."

Example:

makes!: any[];

This removes the error, but it is less safe because TypeScript stops checking that property.

Better typing

Instead of any[], use a real type when possible.

interface Make {
  id: number;
  name: string;
}

makes: Make[] = [];

This gives better autocomplete, safer code, and clearer intent.

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:

  1. The component instance is created.
  2. makes is immediately set to [].
  3. The constructor runs.
  4. Angular calls ngOnInit().
  5. getMakes() starts an asynchronous operation.
  6. Until the response arrives, makes is still [].
  7. When the data arrives, the subscribe callback runs.
  8. this.makes = makes replaces the empty array with real data.

That means the property is safe during the entire component lifecycle.

Now compare that with this version:

makes: string[];

Step by step:

  1. The component instance is created.
  2. makes has no value yet.
  3. The constructor runs.
  4. Angular calls ngOnInit().
  5. The request starts.
  6. Before the response arrives, makes is still undefined.
  7. If code uses makes too early, it may fail.

That is exactly what strict TypeScript is trying to prevent.

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.

Take Quiz

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.makeService.getMakes().subscribe({
    next: makes => {
      this.makes = makes;
    },
    error: err => {
      console.error('Failed to load makes', err);
    }
  });
}

Common pattern: guard in template or code when something may be missing

If a property can truly be absent, developers use checks instead of pretending it is always there.

selectedMake?: Make;

if (!this.selectedMake) {
  return;
}

For list data, though, initializing with [] is usually cleaner than making it optional.

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:

makes!: any[];

Why this can be risky:

  • it hides possible bugs
  • if the value is used before assignment, runtime errors can still happen

Use this only when you are sure the property will be assigned before use.

4. Assuming ngOnInit() counts as constructor initialization

It does not. TypeScript's strict property initialization checks constructor-time assignment, not later lifecycle hooks.

5. Ignoring loading state

Even with makes: Make[] = [], the array may be empty for two reasons:

  • data is still loading
  • the API returned no results

In real apps, developers often track loading separately:

makes: Make[] = [];
isLoading = true;

ngOnInit(): void {
  this.makeService.getMakes().subscribe({
    next: makes => {
      this.makes = makes;
      this.isLoading = false;
    },
    error: () => {
      this.isLoading = false;
    }
  });
}

Comparisons

ApproachExampleWhen to useProsCons
Initialize with default valuemakes: Make[] = []Best for arrays and listsSafe, simple, template-friendlyEmpty array is not the same as "not loaded"
Optional propertymakes?: Make[]When property may truly be missingHonest data modelRequires undefined checks
Definite assignment assertionmakes!: Make[]When framework assigns it later and you are certainRemoves error quicklyLess safe, can hide bugs
Assign in constructorconstructor() { this.makes = []; }When initialization depends on constructor logicValid for strict checksUsually more verbose than field initialization

any[] vs typed array

TypeExampleResult
any[]makes: any[] = []Flexible but unsafe
typed arraymakes: Make[] = []Safer and easier to maintain

For Angular API data, the most common choice is:

makes: Make[] = [];
Take Quiz

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:

  • [] means "currently empty"
  • undefined means "not assigned"
  • Those are not the same thing

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.

Take Quiz

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

  1. Create a Make interface with at least id and name fields.
  2. Declare the makes property using the Make[] type and initialize it safely.
  3. Load data from a service in ngOnInit().
  4. Render the list in the template using *ngFor.
  5. Show a loading message before data arrives.
Take Quiz

Keep learning

Related questions

View more