34
Class Employee {
  firstName: string;
  lastName!: string;
  middleName?: string;
}

What is the difference in these 3 different fields of Employee class?

Live Example

Liam
  • 27,717
  • 28
  • 128
  • 190
  • 1
    https://www.typescriptlang.org/docs/handbook/functions.html#optional-and-default-parameters, https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator – Buczkowski Aug 21 '20 at 12:11
  • 2
    Why not put the full code **in** the question (and fix the `Class`/`class` thing while you were at it)? Why only off-site? – T.J. Crowder Aug 21 '20 at 13:05
  • Does this answer your question? [Type ORM '?' meaning in entities](https://stackoverflow.com/questions/63460693/type-orm-meaning-in-entities) – Evert Aug 22 '20 at 07:36

3 Answers3

27

When having compiler option strictNullChecks: false

If you have strictNullChecks: false in your tsconfig.json then they are exactly the same since having strictNullChecks disabled means that all fields can have null or undefined as valid values.

When having compiler option strictNullChecks: true

class Employee {
   firstName: string;
   lastName!: string;
   middleName?: string;
}

firstName: string means that firstName must be a string. null or undefined are not valid values for firstName.

All uninitialized fields will have a default value of undefined, so this will result in an error Property 'firstName' has no initializer and is not definitely assigned in constructor

To silence the error you need to either change the declaration to firstName: string = 'Some default value' or add a constructor and assign a value for it in the constructor.

constructor() {
    this.firstName = 'some default value';
}

Now for the ! syntax. The lastName!: string syntax is similar to lastName: string in that it basically says that string is the only allowed type. null and undefined are not allowed. But it will silence the compiler about definite assignment error. Let's say you have the following code.

   class Employee {
       firstName: string;
       lastName!: string;
       middleName?: string;

      constructor() {
          // This will silence the compiler about first name not initialized
          this.firstName = 'some default value';
          // The compiler cannot tell that lastName is assigned in init() function
          this.init();
      }
      
      private init(): void {
          this.lastName = 'some default value';
      }
    }

In the previous code, lastName is definitely assigned in the constructor via the this.init() call. However, the compiler cannot know that. So adding the ! is basically telling the compiler "shut up, I know what I am doing". It is up to you then to ensure the correctness of your code.

About the middleName?: string syntax. This is similar to middleName: string | undefined; Since all values has a default value of undefined, the compiler won't complain that middleName is not assigned.

javapedia.net
  • 2,531
  • 4
  • 25
  • 50
Sherif Elmetainy
  • 4,034
  • 1
  • 13
  • 22
19

The ? in that position marks the property optional.

The ! in that position is the definite assignment assertion. It's sort of a declaration-level version of the non-null assertion operator, but used on a property (can also be used on variables) rather than on an expression.

There are two — or arguably three — errors in that example:

  1. Class should be class; JavaScript and TypeScript are case-sensitive.

  2. You need an initializer on firstName (or a constructor that assigns to it unconditionally).

  3. The ! on lastName tells TypeScript that lastName will definitely be assigned, suppressing the kind of error you're getting for firstName, but nothing (in the example) actually does the assignment that using ! there promises TypeScript you know for sure you're doing.

Edit: The code you linked later deals with #1 and #2 above, but not #3. TypeScript won't warn that lastName is never assigned and assumes its value is a string, when in fact it's not there and so reading its value will result in undefined.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • What’s the difference between `lastName!: string;` and `declare lastName: string;`? – chharvey May 01 '21 at 21:58
  • 1
    @T.J.Crowder [you can use `declare` inside a class](https://tsplay.dev/mqQKRm) as of TS3.7 [to support class field \[\[Define\]\] semantics](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier). – jcalz Jan 19 '22 at 14:13
3

They are well hidden in the documentation of TypeScript.

? is described on interfaces, it marks an optional property.

! is the definite assertion operator. It tells the compiler that the property is set (not null or undefined) even if TypeScript's analyses cannot detect so.


Btw, Class is not a TypeScript or JavaScript keyword and produces an error in that position. They keyword for declaring a class is class. TypeScript and JavaScript identifiers and keywords are case sensitive (Class and class are different things).

axiac
  • 68,258
  • 9
  • 99
  • 134