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: stringphoneNumber: string | undefinedaddress: string | undefinedpostalCode: string | undefinedcountry: string | undefined};When we create a user object, we have to provide a value for each property, including the four
string | undefinedproperties.- 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:
undefined
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, country 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, apostalCode, etc. In our tests, we'll find ourselves specifyingundefinedover 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'
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 itphoneNumber?: string. Then the property can be omitted from our objects. If we omit the property and try to access it, we'll getundefined.>
type User = {name: stringphoneNumber?: stringaddress?: stringpostalCode?: stringcountry?: string};const amir: User = {name: 'Amir',};amir.postalCode;Result:
undefined
But how does that actually work? We never gave a value for
postalCode, so where did theundefinedcome 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 typeany, and then accessing a property that definitely doesn't exist.>
const user: any = {name: 'Amir'};user.thisPropertyDefinitelyDoesNotExist;Result:
undefined
When we access an optional property like
{phoneNumber?: string}, we might getundefined. That possibility is reflected in the type that we get back: the actual type ofphoneNumberisstring | undefined. If we try to store aphoneNumberin astringvariable, we'll get a type error.>
type User = {name: stringphoneNumber?: 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'.
Here's a code problem:
The code below tries to create a user without a
lastLoginTimeproperty, but it's causing a type error. Modify the type definition to make thelastLoginTimeproperty optional, which will fix the type error.type User = {name: stringlastLoginTime?: 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']