Pick and Omit

A guild keeps a register of its members. Every member has an id, a number that the guild gives them when they join:

type HeroClass = "warrior" | "mage" | "ranger";

interface Hero {
    id: number;
    name: string;
    heroClass: HeroClass;
    level: number;
}

A function that adds a new member gets a hero without an id, because the guild hands the id out. You could write a second interface with the other three properties, but then you'd have to keep the two in sync by hand. Instead, write Omit<Hero, "id">: the type Hero without the property id. The second type argument is the name of the property, as a literal type in quotes. To leave out several properties, write a union: Omit<Hero, "id" | "level">.

type HeroClass = "warrior" | "mage" | "ranger";

interface Hero {
    id: number;
    name: string;
    heroClass: HeroClass;
    level: number;
}

let nextId = 1;

function addMember(newHero: Omit<Hero, "id">): Hero {
    const hero: Hero = { id: nextId, ...newHero };
    nextId++;
    return hero;
}

console.log(addMember({ name: "Kira", heroClass: "ranger", level: 3 }));
console.log(addMember({ name: "Bruno", heroClass: "warrior", level: 5 }));

This prints:

{ id: 1, name: "Kira", heroClass: "ranger", level: 3 }
{ id: 2, name: "Bruno", heroClass: "warrior", level: 5 }

The new hero is put together with spread from the previous exercise: first the id, then all the properties of newHero. The result has all four properties, so it's a Hero. And when you call the function, TypeScript checks the argument against Omit<Hero, "id">: an id is now a mistake, and so is a missing property:

addMember({ id: 7, name: "Kira", heroClass: "ranger", level: 3 });
addMember({ name: "Lena", heroClass: "mage" });
main.ts(18,13): error TS2353: Object literal may only specify known properties, and 'id' does not exist in type 'Omit<Hero, "id">'.
main.ts(19,11): error TS2741: Property 'level' is missing in type '{ name: string; heroClass: "mage"; }' but required in type 'Omit<Hero, "id">'.

Pick works the other way round: Pick<Hero, "name" | "level"> keeps only the listed properties, so it's { name: string; level: number }. It suits a function that needs only a part of an object:

function badge(hero: Pick<Hero, "name" | "level">): string {
    return `${hero.name}, level ${hero.level}`;
}

You can pass it a whole Hero: it has a name and a level, and the function doesn't look at the rest. Or a smaller object, such as { name: "Lena", level: 1 }. Inside the function, though, there's only name and level. hero.heroClass would be an error: Property 'heroClass' does not exist on type 'Pick<Hero, "level" | "name">'. (TypeScript sorts the texts in such a union alphabetically, so they may come in another order than you wrote them.)

Typical mistakes:

  • A typo in Pick is caught, because the names must be keys of Hero. It's a constraint with keyof, as in the lesson on generics:
    main.ts(10,33): error TS2344: Type '"levle" | "name"' does not satisfy the constraint 'keyof Hero'.
      Type '"levle"' is not assignable to type 'keyof Hero'. Did you mean '"level"'?
  • A typo in Omit isn't caught! Omit<Hero, "idd"> leaves out nothing, the id stays, and there's no error. Check the names in Omit carefully.
  • Several names are one type argument, a union: Pick<Hero, "name" | "level">. With a comma, Pick<Hero, "name", "level">, TypeScript reports Generic type 'Pick' requires 2 type argument(s).

Instructions

Under the comment // Adds a new member, write a function addMember(newHero: Omit<Hero, "id">): Hero. It creates the hero with the id from nextId, increases nextId by one, adds the hero to guild with push and returns it.

Under the function, add two members and print what addMember returns: Kira, a ranger of level 3, and Bruno, a warrior of level 5:

{ id: 1, name: "Kira", heroClass: "ranger", level: 3 }
{ id: 2, name: "Bruno", heroClass: "warrior", level: 5 }

Under it, write a function describeMember(hero: Pick<Hero, "name" | "level">): string that returns the name and the level like this: Kira (level 3).

Under it, in a for...of loop, print what describeMember() returns for every member of guild. Then also print describeMember({ name: "Lena", level: 1 }): Lena isn't in the guild yet, but the function needs only a name and a level. The program ends like this:

Kira (level 3)
Bruno (level 5)
Lena (level 1)

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

2/7