Classes and interfaces are powerful structures that facilitate not just object-oriented programming but also type-checking in TypeScript. A class is a blueprint from which we can create objects that share the same configuration – properties and methods. An interface is a group of related properties and methods that describe an object, but neither provides implementation nor initialisation for them.
Since both of these structures define what an object looks like, both can be used in TypeScript to type our variables. The decision to use a class or an interface truly depends on our use case: type-checking only ? implementation details (typically via creating a new instance)? or even both!
We can use classes for type-checking and the underlying implementation – whereas we cannot with an interface. Understanding what we can get from each structure will easily let us make the best decision that will enhance our code and improve our developer experience.
Using TypeScript class
ES6 introduced class
officially to the JavaScript ecosystem. TypeScript boosts JavaScript classes with extra power such as type-checking and static
properties. This also means that whenever we transpile our code to whatever target JavaScript of our choice, the transpiler will keep all of our class
code present in the transpiled file.
Let’s look at an example of defining a class named PizzaMaker
:
PizzaMaker
is a simple class. It has a static
method called create
. What makes this method special is that we can use it without creating an instance of the class. We just invoke the method on the class directly :
Then, PizzaMaker.create()
returns a new object – not a class – with a name
and toppings
properties defined from the object passed to it as argument.
If PizzaMaker
did not define create
as a static
method, then to use the method we would need to create an instance of PizzaMaker
:
We get the same output we had with create
as a static
method. Being able to use TypeScript classes with and without an existing instance of a class makes them extremely versatile and flexible. Adding static
properties and methods to a class makes them act like a singleton while defining non-static properties and methods make them act like a factory.
Now, unique to TypeScript is the ability to use classes for type-checking. Let’s declare a class that defines what a Pizza
looks like:
In the Pizza
class definition, we are using a handy TypeScript shorthand to define class properties from the arguments of the constructor – it saves a lot of typing! Pizza
can create objects that have a name
and a toppings
property:
Therefore, we can use the Pizza
class to type-check the event
argument of PizzaMaker.create(...)
:
We’ve made PizzaMaker
much more declarative, and hence, much more readable. Not only that, but if we need to enforce the same object structure defined in Pizza
in other places, we now have a portable construct to do so! Just append export
to the definition of Pizza
and you get access to it from anywhere in your application.
export class Pizza {
constructor(public name: string, public toppings: string[]) {}
}
Using
Pizza
as a class is great if we want to define and create aPizza
, but what if we only want to define the structure of aPizza
but we’d never need to instantiate it? That’s wheninterface
comes handy!
Using TypeScript interface
Unlike classes, an interface
is a virtual structure that only exists within the context of TypeScript. The TypeScript compiler uses interfaces solely for type-checking purposes. Once your code is transpiled to its target language, it will be stripped from its interfaces – JavaScript isn’t typed, there’s no use for them there. Also we will use interface only on development cycle and for testing purpose .
While a class may define a factory
or a singleton
by providing initialisation to its properties and implementation to its methods, an interface
is simply a structural contract that defines what the properties of an object should have as a name and as a type. How you implement or initialise the properties declared within the interface
is not relevant to it. Let’s see an example by transforming our Pizza
class into a Pizza
interface:
our current code provides type-checking for Pizza
but can’t create a pizza. For doing that, we have to first initialise an object of type interface. Then we can use this object with the static function create() of the class PizzaMaker.