Execute Program

Everyday TypeScript: Optional Properties

Welcome to the Optional Properties lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • We often work with optional properties in objects. For example, our user records might have phone numbers and various address properties, but only for some users. We can model those properties with a union type like string | undefined.

  • >
    type User = {
    name: string
    phoneNumber: string | undefined
    address: string | undefined
    postalCode: string | undefined
    country: string | undefined
    };
  • When we create a user object, we have to provide a value for each property, including the four string | undefined properties.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const amir: User = {
    name: 'Amir',
    phoneNumber: undefined,
    address: undefined,
    postalCode: undefined,
    country: undefined,
    };

    amir.phoneNumber;
    Result:
    undefinedPass Icon
  • If we omit any of the properties, we get a type error.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const amir: User = {
    name: 'Amir',
    };

    amir.phoneNumber;
    Result:
    type error: Type '{ name: string; }' is missing the following properties from type 'User': phoneNumber, address, postalCode, countryPass Icon
  • Now imagine that we're writing automated tests for our users. Each test is only concerned with one small part of the system, so most tests won't need a phoneNumber, a postalCode, etc. In our tests, we'll find ourselves specifying undefined over and over again every time we create a user:

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const user: User = {
    name: 'Betty',
    phoneNumber: undefined,
    address: undefined,
    postalCode: undefined,
    country: undefined,
    };

    user.name;
    Result:
    'Betty'Pass Icon
  • That verbosity makes code harder to read. Fortunately, TypeScript has a simple, clean solution: optional property types. Instead of phoneNumber: string, we add a question mark to make it phoneNumber?: string. Then the property can be omitted from our objects. If we omit the property and try to access it, we'll get undefined.

  • >
    type User = {
    name: string
    phoneNumber?: string
    address?: string
    postalCode?: string
    country?: string
    };

    const amir: User = {
    name: 'Amir',
    };

    amir.postalCode;
    Result:
    undefinedPass Icon
  • But how does that actually work? We never gave a value for postalCode, so where did the undefined come from?

  • This is one of many places where TypeScript reuses JavaScript's existing behavior in a clever way. In JavaScript, accessing properties that don't exist gives undefined. We can check that by creating an object, giving it the TypeScript type any, and then accessing a property that definitely doesn't exist.

  • >
    const user: any = {
    name: 'Amir'
    };

    user.thisPropertyDefinitelyDoesNotExist;
    Result:
    undefinedPass Icon
  • When we access an optional property like {phoneNumber?: string}, we might get undefined. That possibility is reflected in the type that we get back: the actual type of phoneNumber is string | undefined. If we try to store a phoneNumber in a string variable, we'll get a type error.

  • >
    type User = {
    name: string
    phoneNumber?: string
    };

    const amir: User = {name: 'Amir'};

    const phoneNumber: string = amir.phoneNumber;
    phoneNumber;
    Result:
    type error: Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.Pass Icon
  • Here's a code problem:

    The code below tries to create a user without a lastLoginTime property, but it's causing a type error. Modify the type definition to make the lastLoginTime property optional, which will fix the type error.

    type User = {
    name: string
    lastLoginTime?: string
    };
    const amir: User = {name: 'Amir'};
    const betty: User = {name: 'Betty', lastLoginTime: '2021-08-31'};
    [amir.name, betty.name];
    Goal:
    ['Amir', 'Betty']
    Yours:
    ['Amir', 'Betty']Pass Icon