Interface in a Class in TypeScript
TypeScript’s interface-class relationship is essential to using the language’s static typing features in an object-oriented framework. Because it is structurally typed, TypeScript mostly depends on interfaces to provide the desired “shape” or organisation of variables and objects. The constructs that provide these shapes tangible, executable implementations are called classes.
One important difference is that interfaces only exist inside the TypeScript type system and don’t affect JavaScript at runtime. Interface declarations are lost during the compilation (transpilation) of TypeScript code to JavaScript. Classes, on the other hand, compile to ES6 classes or JavaScript functions that reflect the structure and behaviour of the runtime.
Class Implementation using
With TypeScript, a class can use the implements
keyword to explicitly state that its instances follow the structure specified by one or more interfaces. By precisely providing all of the attributes and methods specified in the interface, this keyword acts as a vital compile-time safety check.
Structure and Enforcement
A class that implements an interface needs to contain all of the fields and functions that the interface specifies. A missing member will be indicated and the codebase will stay in sync if the external interface structure changes because the compiler will detect the incompatibility right at the class declaration site.
Only the class instances’ structure is subject to the constraint imposed by implements
. Additionally, classes may implement more than one interface, in which case they must meet the shape specifications of each interface that is given.
This process makes polymorphism possible. Classes implementing interfaces ensure that they are compatible types, allowing for flexible dependency injection and substitution, because any object that structurally matches an interface can be considered as that interface’s type (Duck Typing).
Code Example: Implementing an Interface
Here is a demonstration of a class implementing a Movable
interface:
TypeScript Code (Implements Interface)
interface Movable {
position: number;
move(distance: number): void;
}
class Car implements Movable {
// 1. Must define all properties required by Movable
public position: number = 0;
// Private members are allowed but ignored by the interface
private speed: number = 42;
// Constructor to initialize
constructor() {
// implementation details
}
// 2. Must implement all methods required by Movable
public move(distance: number): void {
this.position += distance;
console.log(`Car moved to position: ${this.position}`);
}
}
// Example usage
const myCar = new Car();
myCar.move(10);
// myCar now has the structural type Movable
Output/Explanation:
If Movable
required position: number
but Car
omitted it or provided position: string
, a compile-time error would occur. The class Car
is now guaranteed to have the necessary position
field and move
method.
Car moved to position: 10
Interfaces Defining Constructors
Although interfaces specify a class’s instance shape, they can also specify the shape of the constructor function. This feature is essential for accurately typing classes that are handled as values instead than just types for their instances. At the type level, classes produce two terms: one for the class constructor (which may be accessed using the typeof
operator) and one for the instance.
The construct signature, which is preceded with the new
keyword and syntactically resembles a function call signature, is used by interfaces to create constructors.
Structure of a Constructor Interface
The expected arguments for instantiation and the type of returned instance are specified in a construct signature. When defining factory patterns or sending a class as a parameter to a function (dependency injection), this pattern is commonly utilised.
This is how a constructor interface’s type signature appears: : new(parameterList): returnType
.
Code Example: Interface for a Constructor
Any conforming class must generate an instance of type NamedItem
and accept a string parameter in its constructor, according to IConstructable
.
// Define the shape of the instance
interface NamedItem {
name: string;
}
// Define the shape of the constructor
interface IConstructable {
new (label: string): NamedItem;
}
// A function that requires a class/constructor adhering to IConstructable
function createItem(ctor: IConstructable): NamedItem {
return new ctor("Generated Name");
}
// A class that structurally conforms to NamedItem
class MyNamedItem implements NamedItem {
name: string;
constructor(label: string) {
this.name = `Item: ${label}`;
}
}
// Usage: MyNamedItem adheres to IConstructable structurally
const item = createItem(MyNamedItem);
console.log(item.name);
Output/Explanation:
The createItem
function requires a constructor that can be called with new
and one string argument. MyNamedItem
satisfies this requirement.
Item: Generated Name
Structural vs. Nominal Typing
TypeScript uses a structural type system. This indicates that, unlike nominal systems like C# or Java, which rely on explicit declarations or inheritance chains to determine type compatibility, types are based on the members they hold. The saying “if it looks like a duck and quacks like a duck, it’s probably a duck” is frequently used to illustrate the idea.
- Implicit Implementation: If an object’s shape satisfies the interface’s requirements, it can be assigned to an interface type even if the class did not explicitly use the
implements
keyword thanks to structural typing. - Explicit Enforcement: Although implementation is implicit, developers usually use
implements
to enforce the contract at the declaration stage when defining a class. As a precaution, it instructs the compiler to verify compatibility beforehand rather than waiting for an error when the class instance is subsequently utilised in another context.
In Conclusion
Classes give real runtime implementations, whereas interfaces define abstract, development-time structural contracts. The implements
keyword supports a crucial component of TypeScript’s type-safe polymorphism by explicitly connecting a class to an interface for compile-time enforcement.