TypeScript Generic Interfaces in Depth
A generic interface is a contract with a type slot that each use fills in. One interface can describe envelopes, stores and services for many payload types.
An envelope interface wraps any payload
ApiResponse carries a data member plus fixed status members. The annotation fills the data slot with a product shape.
interface ApiResponse<T> {
data: T;
ok: boolean;
message: string;
}
interface Product {
id: number;
name: string;
}
const response: ApiResponse<Product> = {
data: { id: 1, name: 'Pen' },
ok: true,
message: 'loaded',
};
console.log(response.data.name);
console.log(response.message);A plain class implements the contract
Repository is the generic contract for storing one item type. ProductRepository is a plain class that fills the slot with Product.
interface Repository<T> {
add(item: T): void;
all(): T[];
}
interface Product {
id: number;
name: string;
}
class ProductRepository implements Repository<Product> {
private items: Product[] = [];
add(item: Product): void {
this.items.push(item);
}
all(): Product[] {
return this.items;
}
}
const repo = new ProductRepository();
repo.add({ id: 1, name: 'Pen' });
console.log(repo.all());The same shape as a type alias
An alias writes the same envelope shape after an equals sign. Both spellings produce the same member types for a given payload.
interface Envelope<T> {
data: T;
ok: boolean;
}
type EnvelopeAlias<T> = {
data: T;
ok: boolean;
};
const first: Envelope<string> = { data: 'pen', ok: true };
const second: EnvelopeAlias<string> = { data: 'ruler', ok: false };
console.log(first.data);
console.log(second.data);Example
interface Repository<T> {
add(item: T): void;
all(): T[];
}
interface ApiResponse<T> {
data: T;
ok: boolean;
}
class NameRepository implements Repository<string> {
private items: string[] = [];
add(item: string): void {
this.items.push(item);
}
all(): string[] {
return this.items;
}
}
const repo = new NameRepository();
repo.add('pen');
repo.add('ruler');
const response: ApiResponse<string[]> = { data: repo.all(), ok: true };
console.log(response.data);
console.log(response.ok);Key points
- The type parameter sits after the interface name and appears in its members.
- Each annotation fills the slot with one concrete payload type.
- A class implements a generic interface by naming the concrete type in the implements clause.
- The implementing class must provide every member with the filled-in types.
- A generic type alias can spell the same shape with an equals sign.
In this example, the repository contract is filled with string, and the envelope contract is filled with the array that the repository returns. Two generic interfaces compose because each slot takes a complete type.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท TypeScript Playground
Exercise
How does ProductRepository use the generic Repository interface?