In later versions of TypeScript there’s the concept of a Strict Class Property Initializer, or Definite Assignment Assertion.

This feature all of a sudden produced type errors on previously working code, so let’s explore why it’s here and why you should be using it.

“Property … has no initializer and is not definitely assigned in the constructor.”

Sound familiar? Welcome to strict initialization and the requirement of definite assignment.

So, what it is? It’s a safer way to declare properties and how you expect the data to be made available in your application.

If you see this error, your property is simply being defined with no value. Or at least, no obvious value.

It’s most likely that you’ve seen this error in an Angular context, for example:

@Component({...})
export class PizzaComponent {
  // ❌ Property pizza$ has no initializer and is not definitely assigned in the constructor
  pizza$: Observable<Pizza>;
  
  constructor(private store: PizzaStore) {}
  
  ngOnInit() {
    this.pizza$ = this.store.select((state) => state.pizzas);
  }
}

So how do we ‘fix it’? Well, there are a few ways. First, we can declare a ! after the property (using definite assignment):

@Component({...})
export class PizzaComponent {
  // ✅ Correct syntax with no errors
  pizza$!: Observable<Pizza>;
  
  constructor(private store: PizzaStore) {}
  
  ngOnInit() {
    this.pizza$ = this.store.select((state) => state.pizzas);
  }
}

What it means is: “TypeScript, I know there is no data here yet, but I’m confident it will be there soon, just trust me!”.

This is the preferred option, and I’d encourage you to stick with it.

Alternatively, you can supply a default value, and then it’s not needed (but this involves writing and importing code that doesn’t really do anything, I’m not a fan but it works):

@Component({...})
export class PizzaComponent {
  // ✅ No errors, but a bit of a pointless empty initialization
  pizza$: Observable<Pizza> = of([]);
  
  constructor(private store: PizzaStore) {}
  
  ngOnInit() {
    this.pizza$ = this.store.select((state) => state.pizzas);
  }
}

Part of the error we see is “not defined in the constructor”, so by also doing that we can ‘fix’ it (but don’t do this):

@Component({...})
export class PizzaComponent {
  // ✅ Correct syntax with no errors
  pizza$: Observable<Pizza>;
  
  constructor(private store: PizzaStore) {
    // ❌ But assignment in the constructor is not recommended
    this.pizza$ = this.store.select((state) => state.pizzas);
  }
}

Why don’t do this? Well, we’ve removed ngOnInit which is a great practice and you should be using it. Angular knows about ngOnInit, whereas JavaScript knows about the constructor. Angular has no control, therefore we should move to a lifecycle hook.

💥 Read my deep-dive on the difference between NgOnInit and the constructor.

You can (at your own peril) disable this feature in your tsconfig.json file as well, and possibly need to disable other strict TypeScript compiler options:

{
  "compilerOptions": {
    "strictPropertyInitialization": false,
  },
}

In a non-Angular context, it’s likely you’ll want to structure your data, properties and initializations better to avoid introducing bugs and other errors.

 

Using the ! definite assignment in Angular is a safe and acceptable solution to writing components, services, directives and more - as demonstrated with OnInit, we can be sure we’re adopting the correct practice.


#ts #typescript 

TypeScript Strict Property Initialization
1.00 GEEK