Learning TypeScript 02 — Interfaces
Interfaces are common in other languages, but we didn’t have it in JavaScript, thanks to interfaces we can “implement contracts” in our classes.
But.. why?
Yes, why I need something that I never had? This question is so usual in Frontend, because is evolving a lot these years and a sometimes is adopting parts and ideas from the Backend languages.
I’ve asked once to a guy about IOC (Inversion of Control) in JavaScript, why he implement it on his projects and he answered me that he doesn’t need it because he can use require.
This is a common behavior in a lot of Frontend people, for me, the Frontend is some years behind the Backend and is learning the things little by little, some examples are Classes, Async await, Design patterns, etc.
Ok, but why are interfaces important in Backend?
There are a lot of cases where interfaces can help you, let’s see some of them:
- You want to inject a service into another one and you want to be able to mock it easily.
// Using functions to simplify the example
function DevelopingService(IUserService userService) {
const users = userService.get();
}class DevelopingService {
constructor(IUserService userService) {
const users = userService.get();
}
}
If I want to test DevelopingService I don’t want to have a real userService because my test has to be isolated from the dependencies:
const userServiceMock = {...};
const developers = new DevelopingService(userServiceMock);
If userServiceMock and userService are implementing the interface IUserService, the test will work easily. (We are going to see more about that in the article about classes)
- Another nice example if you are implementing ajax calls to a server and you want to mock it before:
// Example super simplified
const useMock = ...;class DevelopingService {
constructor(IUserData userData) {
userService.get().then(results => console.log(results));
}
}const userData = useMock ? new UserDataJSON() : new UserData();
new DevelopingService(userData);
And some other examples.
Basically, the interfaces are a nice tool that helps us in some scenarios.
Another great scenario where interfaces help us are to define a type of an object.
Usually, we use Models to define an object, this is a simple example:
// Super simple class like JavaScript, we will se TypeScript classes later
class Developer {
name: string;
constructor(name: string) {
this.name = name;
}
}const developers = [new Developer('Quique'), new Developer('Olga')];
Ok, but if we don’t have the case where we are creating new instances of this model? If we have the object and we only want to type it?
// Super simple class like JavaScript, we will se TypeScript classes later
class Developer {
name: string;
constructor(name: string) {
this.name = name;
}
}developerService.get().then((developers: Developer[]) => {...});
Yes! this works fine but we are creating unnecessary JavaScript file when we compile that. A nice thing about interfaces is that are compiled to nothing because them only exists in TypeScript, not in JavaScript.
If we don’t need a class, we don’t have to use it:
interface Developer {...}developerService.get().then((developers: Developer[]) => {...});
It works!
Now you’re convinced that interfaces are nice, let's continue to understand how they work.
Note: Only add public methods and properties to your interfaces
Declaring a simple interface:
interface IDeveloper {
name: string;
age: number;
getPicture(): string;
}
This interface has two simple properties and a method defined.
These are some cases where I can use it:
class Developer implements IDeveloper {...}
const dev: IDeveloper = {...}
function developing(dev: IDeveloper): IDeveloper {...}
Note: If we use the interface like a model, usually we don’t use the ‘I’ prefix.
interface Developer {
name: string;
age: number;
}const dev: Developer = {...}
function developing(dev: Developer): Developer {...}
But interfaces can have also optional properties:
interface IDeveloper {
name: string;
age?: number;
}// OK
class dev implements IDeveloper {
name = 'Alex';
age = 20;
}// OK
class dev2 implements IDeveloper {
name = 'Alex';
}// Error
class dev3 implements IDeveloper {
name = 'Alex';
age = '9';
}
Sometimes we don’t have so much information about the model (for example is dynamic, or comes from a server) but there is always a way instead use any.
You can ‘index signature parameter types’:
interface Developer {
[key: string]: string;
}// Ok
const dev: Developer = {
a: 'a',
b: 'b'
};// Error
const dev2: Developer = {
a: 'a',
b: 'b',
c: 1
};
Wow! That was a lot about interfaces 💪🏼
You can find more info about this article in the official guide.
And info about why I’m doing this articles about TypeScript.
Let’s continue with the next chapter: Classes.
And remember, feedback is welcome 🤙.