1

When I declare a variable in typescript like below:

id: number

I get the following error:

Property 'id' has no initializer and is not definitely assigned in the constructor.ts(2564)

If I change the variable to one of the following, the error goes away:

  • id?: number
  • id!: number

What are the differences between those and what situations I should use the above?

wonderful world
  • 10,969
  • 20
  • 97
  • 194
  • 1
    `id?` means that the variable can be `null` or `undefined` and `id!` means it will be initialized, but not in the constructor. This should be explained in the Typescript documentation – UnholySheep Oct 09 '21 at 20:21
  • 2
    @UnholySheep `id?` does not include `null` in the domain – jcalz Oct 09 '21 at 20:22
  • 1
    And you can't use `?` with locals ("variables"), only properties and parameters. – Dai Oct 09 '21 at 20:23
  • 1
    @jcalz Ah, right my bad - I mistakenly thought it includes the same types as the non-null assertion operator – UnholySheep Oct 09 '21 at 20:25
  • One of you can post the answer instead of a comment. I will mark that as answer. – wonderful world Oct 09 '21 at 20:31
  • 3
    @wonderfulworld I sincerely doubt this is not a duplicate. – VLAZ Oct 09 '21 at 20:34
  • [What does !: mean in Typescript?](https://stackoverflow.com/q/50983838) | [!: (bang colon) notation in Angular](https://stackoverflow.com/q/58073841) | [Exclamation Mark in type definition](https://stackoverflow.com/q/57816914) | [What is the question mark for in a Typescript parameter name](https://stackoverflow.com/q/37632760) – VLAZ Oct 09 '21 at 20:40
  • I don't think my question is duplicate. The ? discussed in that question was in the context of angular Javascript framework. – wonderful world Oct 09 '21 at 21:24
  • @wonderfulworld why do you think Angular uses any other language than TypeScript? Angular is simply a framework. It doesn't change how language features work. – VLAZ Oct 11 '21 at 05:12

1 Answers1

5

When you write this:

class Optional {
  id?: number;
}

you have declared that id is an optional property. That means it may or may not be present in an instance of Optional. If it is present, the value should be a number (or possibly undefined unless you are using the --exactOptionalPropertyTypes compiler option). If it is not present, then if you read the id property, the value will be undefined.

The above resolves the compiler warning because you are not required to initialize a property that's optional; it will just be undefined when you read it.

Anyway that means you cannot just use the id property as a number:

const o = new Optional();
o.id.toFixed(2) // compiler error!
//~~ <-- Object is possibly undefined

That warning is good, right? Because you don't want to accidentally read the toFixed() method of undefined. You need to check for undefined first, possibly by using the optional chaining operator (?.):

o.id?.toFixed(2) // okay

Optional properties are a reasonable approach when you are not sure if the properties will be assigned by the time users want to access them, and you want to protect these users from potential runtime errors.


On the other hand, when you write this:

class Asserted {
  id!: number
}

you have asserted that the id property has definitely been assigned. That assertion is you telling the compiler something it can't verify for itself. This is useful in situations where you actually do assign the property but the compiler cannot follow the logic:

class ActuallyAssigned {
  id!: number
  constructor() {
    Object.assign(this, { id: 123 });
  }
}

An instance of ActuallyAssigned will have the id property set in the constructor, because the Object.assign() function copies properties into the first parameter from all subsequent parameters. But the compiler cannot understand this, and so without the definite assignment assertion, you'd get a warning. Silencing that warning is reasonable here.

But in Asserted above, it's not reasonable. You have lied to the compiler. It can't see that id has be assigned because it actually has not been assigned. And so you will not get a compiler warning if you go ahead and treat the id property like a number, even though it might be undefined:

a.id.toFixed(2) // no compiler error, but
//  RUNTIME ERROR: o.id is undefined

Definite assignment assertions are therefore not recommended for situations when properties will quite possibly be undefined when users want to access them.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360