Execute Program

Everyday TypeScript: Partial in Practice

Welcome to the Partial in Practice lesson!

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

  • We saw JavaScript's spread syntax in an earlier lesson: {...obj1, ...obj2} lets us build new objects out of multiple old objects. But what if the types are User and Partial<User>, like in {...user, ...partialUser}?

  • In that case, we're building an object with all of user's properties, but partialUser may override some of them. Here are some concrete examples of that. In each example, we combine two objects.

  • In the first example, we have a "template", like a user object with all of its properties, and we have some "overrides", like a partial user object. Here, the "template" is created as its own variable that lives outside of the function.

  • >
    type User = {
    name: string
    country: string
    };

    const template: User = {
    name: 'Amir',
    country: 'France',
    };

    /* This function combines the template object and the overrides to
    * produce a new object. */
    function override(overrides: Partial<User>): User {
    return {...template, ...overrides};
    }
  • If we provide no overrides, we get the original object back.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    override({});
    Result:
    {name: 'Amir', country: 'France'}Pass Icon
  • We can override individual properties as long as the types match.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    override({name: 'Betty'});
    Result:
    {name: 'Betty', country: 'France'}Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    override({country: 'unknown'});
    Result:
    {name: 'Amir', country: 'unknown'}Pass Icon
  • If we provide the wrong type for a property, that's a type error.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    override({country: 75010});
    Result:
    type error: Type 'number' is not assignable to type 'string'.Pass Icon
  • If we pass unexpected properties in an object literal, that's also a type error.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    override({age: 36});
    Result:
    type error: Object literal may only specify known properties, and 'age' does not exist in type 'Partial<User>'.Pass Icon
  • That's a lot of flexibility in one small function!

  • Functions like override are useful in many situations, most notably in unit tests. When we're testing our user module, we could create a new user object from scratch in every test. But user objects often have dozens of properties, and most tests only care about one or at most a few of them.

  • Our override function solves that problem perfectly. We define one template user object, then use override to tweak only the properties that are relevant to each test. In a test of email validation we might call override({email: 'invalid!@#$%^&*email@example.com'}), and in another test we might call override({admin: true}). That's much nicer than specifying two dozen properties every time.

  • One final note about using Partial to override properties. So far, our override function has known about the User type. But it doesn't have to! We can make it generic, taking both the template object and the overrides as function parameters.

  • >
    /* This override function is generic and works with any object type! */
    function override<T>(template: T, overrides: Partial<T>): T {
    return {...template, ...overrides};
    }
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    type User = {
    name: string
    country: string
    };

    const template: User = {
    name: 'Amir',
    country: 'France',
    };

    const user = override(template, {name: 'Betty'});
    user;
    Result:
    {name: 'Betty', country: 'France'}Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    type Album = {
    name: string
    lengthMinutes: number
    };

    const template: Album = {
    name: 'Kind of Blue',
    lengthMinutes: 45,
    };

    const album = override(template, {lengthMinutes: 46});
    album;
    Result:
    {name: 'Kind of Blue', lengthMinutes: 46}Pass Icon
  • That little three-line override function does a lot of work:

    • It overrides properties at runtime.
    • It works for any object type.
    • It ensures that we only override properties that exist on the type.