Borislav Hadzhiev
Tue Feb 15 2022·4 min read
Photo by Max Libertine
Use the extends
keyword to extend interfaces in TypeScript, e.g.
interface Dog extends Animal {age: number;}
. The extends
keyword allows us
to copy the members from other named types and add new members to the final,
more generic interface.
interface Animal { name: string; age: number; } interface Dog extends Animal { run(): void; } const a1: Dog = { name: 'Tom', age: 3, run() { console.log('the dog runs...'); }, };
The extends keyword removes the need of having to repeat the members of other types at multiple places and shows the relation between the interfaces to the reader of the code.
You can extend from multiple interfaces by separating them with a comma.
interface Dog { name: string; } interface Shepherd { guardian: boolean; } interface Animal extends Dog, Shepherd { age: number; } const a1: Animal = { name: 'Tom', guardian: true, age: 3, };
The main benefits of extending an interface are:
You are not required to add any new members to the final interface and can use
the extends
keyword to simply combine interfaces.
interface Dog { name: string; } interface Shepherd { guardian: boolean; } // 👇️ Combine the Dog and Shepherd interfaces interface Animal extends Dog, Shepherd {} // 👇️ Alternatively use intersection type type Animal2 = Dog & Shepherd; const a1: Animal = { name: 'Tom', guardian: true, };
You can also use the extends
keyword to extend an interface with existing type
aliases.
type Dog = { name: string; age: number; }; interface Shepherd { guardian: boolean; } interface Animal extends Dog, Shepherd {} const a1: Animal = { name: 'Tom', age: 3, guardian: true, };
The main reason to extend an interface is that it signals to the reader of the code that the types we are combining are related in some way.
If we just repeat the fields between the interfaces, the connection is lost.
extends
keyword is - we are copying members from other more specific, named types and adding any new members we want to construct a final more generic interface.If you need to override a property from the interface you extend, you can use
the Omit
utility type.
interface Employee { id: number; name: string; salary: number; } // for multiple use - Omit<Employee, 'id' | 'salary'> interface Developer extends Omit<Employee, 'id'> { id: string; // 👈️ override type of id language: string; } const dev: Developer = { id: 'dev-1', name: 'Tom', salary: 100, language: 'TypeScript', };
The Omit utility type constructs a new type by picking the properties from the provided type and removing the specified keys.
id
property from the Employee
type when extending from it, so we can override its type.Had we not removed the property and tried to specify a different type in the
Developer
interface, we would have gotten an error.
interface Employee { id: number; name: string; salary: number; } // ⛔️ Error: Interface 'Developer' incorrectly extends interface 'Employee'. // Types of property 'id' are incompatible. // Type 'string' is not assignable to type 'number'.ts(2430) interface Developer extends Employee { id: string; // 👈️ override type of id language: string; }
The best way to get around this is to remove the type when extending from the interface and override it in the new interface.
You can use a union of string literals to omit multiple properties from a type when extending.
interface Employee { id: number; name: string; salary: number; } // 👇️ omit `id` and `salary` interface Developer extends Omit<Employee, 'id' | 'salary'> { id: string; // 👈️ override type of id salary: string; // 👈️ override type of salary language: string; } const dev: Developer = { id: 'dev-1', name: 'Tom', salary: '50 K', language: 'TypeScript', };
To pass multiple properties to the the Omit
utility type, separate the
properties with a pipe |
.
Note that when extending from an interface, you can provide a more narrow type for the same property name.
interface Employee { id: number | string; // 👈️ number OR string (wide) name: string; salary: number; } interface Developer extends Employee { id: string; // 👈️ only string (narrow) language: string; } const dev: Developer = { id: 'dev-1', // 👈️ can only be string now name: 'Tom', salary: 100, language: 'TypeScript', };
The id
property in the Employee
interface has a type of number
or
string
, but when extending from the interface, we specified a more narrow type
of string
.
The id
property in the Developer
interface can only be of type string
.
Omit
utility type to remove the specific property and override its type.You can use the implements clause to enforce that a class satisfies one or more interfaces.
interface Dog { name: string; run(): void; } interface Shepherd extends Dog { guardian: boolean; } // ⛔ ️Error: Class 'Animal' incorrectly implements interface 'Shepherd'. // Type 'Animal' is missing the following properties // from type 'Shepherd': guardian, name, runts(2420) class Animal implements Shepherd {}
The example shows that we get an error, because the class fails to correctly implement the interface.
To solve the error, make sure to correctly implement the interface in the class.
interface Dog { name: string; run(): void; } interface Shepherd extends Dog { guardian: boolean; } class Animal implements Shepherd { name = 'Tom'; guardian = true; run() { console.log('The animal runs'); } }