Class patterns for static interfaces
Tags: typescript, class, programming, javascript
Published: 2022-04-06 15:29:48

When working with classes, there are two types to consider: the type of the static side and the type of the instance side.

The most common is of course the instance side, and things are working like expected :

```typescript
interface HasName {
    name: string
}

class Person implements HasName {
    name!: string
    birthDate!: Date
}
```

In the example above, all the fields are describing an instance of a `Person`, that is defined partially with the interface `HasName`.

Then, we might add some convenient static methods :

```typescript
class Person implements HasName {
    name!: string
    birthDate!: Date
    static async load(name: string): Promise<Person> {
        // get data and create a new Person someway
    }
    static async save(person: Person) {
        // save the Person someway
    }
}

// try it :
const bob = await Person.load('Bob')
bob.birthDate.getFullYear();
```

At this point of the design of such outstanding application, we guess that some other classes will follow the same pattern for loading and saving data, therefore we create an interface for that purpose :

```typescript
interface Storable<Type, Key> {
    load(key: Key): Promise<Type>
    save(item: Type): Promise<void>
}
```

Since the `static` keyword can't be used on interfaces, I just removed it, but how to enforce my `Person` class to implement those methods as static ?

# Language evolution proposal

There has been [some discussions on that topic](https://github.com/microsoft/TypeScript/issues/13462), but my preferred solution would be :

```typescript
// unfortunately, this is not allowed by Typescript :
class Person implements HasName, static Storable<Person, string> {
    name!: string
    birthDate!: Date
    static async load(name: string): Promise<Person> {
        // get data and create a new Person someway
    }
    static async save(person: Person) {
        // save the Person someway
    }
}
```

Unlike other proposals, it has the advantage to not restrict the interface to classes and keep the intend of Typescript of being a structural type system.

But that syntax doesn't exist : we must use the possibilities of the language.

# Understanding class type duality

Let's recall that 2 types exist around each class :

- the type of the **static side**,
- the type of the **instance side**.

Before showing acceptable patterns, we must understand what is the type of the **static side** of a class. For that purpose, let's examine how they are managed on predefined classes, say let's have a look at `String` :

```typescript
interface String {
    charAt(pos: number): string;
    charCodeAt(index: number): number;
    // etc, all instance methods
}

interface StringConstructor {
    new(value?: any): String;
    (value?: any): string;
    readonly prototype: String;
    fromCharCode(...codes: number[]): string;
}

declare var String: StringConstructor;
```

So, `String` instances are fully defined by the interface definition, and at the end, the same `String` stuff has a type, the `StringConstructor` interface.

> Strictly speaking, the name endorsed is not the best choice, because the string constructor is just the function `new(value?: any): String;` : all other definitions of that interface are static definitions, therefore the name `StringStatic` would have certainly been a better choice ; but let's refrain from rewriting history...

In fact, the last line `declare var String: StringConstructor` is used to enforce the built-in variable `String` to be of the type `StringConstructor`. But `String` as a variable doesn't refer to an instance but to the class.

Therefore, we might apply the same recipe for our own classes : what is needed is just **a typed variable** to which one can assign the class, it will enforce the class to honour that type.

# Pattern 1 : class expression

Let's try it on our type `Person` ; first we can make some observations if we assign that class to some variable (actually, a constant !) :

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/al8i1icj2nn1fx7f5tpc.png)

In our preferred IDE, we see that `person` has the type `typeof Person` : this is the static side of the class `Person`. Unfortunately, unlike with the built-in String, we can't call the constant `Person` because that symbol already exist in the value space for the class itself, therefore we call it `person`.

Hopefully, we can use class expressions:

```typescript
interface PersonInterface extends HasName {
    birthDate: Date
}
interface PersonStatic extends Storable<PersonInterface, string> {
    new(): PersonInterface
}
const Person: PersonStatic = class Person implements HasName {
    name!: string
    birthDate!: Date
    static async load(name: string): Promise<PersonInterface> {
        // get data and create a new Person someway
    }
    static async save(person: Person) {
        // save the Person someway
    }
}
```

Ouch ! There are too much disadvantages with this pattern :

1. we can't apply a decorator on the class
1. generics are left out (if we had `Person<Foo>` ?)
1. lots of boilerplate code are necessary

Let's take a closer look at the last issue. Why do we have this new interface `PersonInterface` ? This is because we lost the type `typeof Person` of the class : if we use `Storable<typeof Person>`, it would refer the type of the constant `Person` which is not what we want. On the other hand, `Storable<Person>` is not usable since `Person` is a constant. Do you follow ? Well... we need that new interface `PersonInterface`.

In fact, we have to fix the things lost when using the class as an expression.

# Pattern 2 : static field of itself

One better solution, is to have **a standalone typed variable that refers the class**. But wait, where is the preferred place to define such a variable ? In the class itself as a static variable of course ! But wait, could we define an interface for such classes ? Sure we can :

```typescript
interface HasStatic<Type> {
    class: HasStatic<Type>
}
```

Then, we can define a type for the static side of our class :

```typescript
type PersonStatic = Storable<Person, string> & HasStatic<Person>;

class Person implements HasName {
    static class: PersonStatic = Person; // 👈 
    name!: string
    birthDate!: Date
    static async load(name: string): Promise<Person> {
        // get data and create a new Person someway
    }
    static async save(person: Person) {
        // save the Person someway
    }
}
```

The thing that enforce our static methods is due to the line `static class: PersonStatic = Person` : it has the type `PersonStatic` and has the value `Person`, which is the class itself.

I personally like that solution because it is in the spirit of the language evolution that I suggest before.

# Pattern 3 : static block with field of itself

The following pattern is a variant of the previous one :

```typescript
class Person implements HasName {
    static {
        const clazz: Storable<Person, string> = Person;
    }
    name!: string
    birthDate!: Date
    static async load(name: string): Promise<Person> {
        // get data and create a new Person someway
    }
    static async save(person: Person) {
        // save the Person someway
    }
}
```

It has the advantage of being more straightforward and to drop the `clazz` constant after being set. Notice that `class` couldn't be used as the constant name; hence `clazz`.

# Pattern 4 : class decorator

Another solution is to call a function that takes as argument the static definition of the class. The best way to apply that function on our class is by using the semantic of the decorator.

Here is the decorator factory, that does nothing and returns nothing, which means that the decorated class is not altered ; its role is to enforce its argument (the static side of our class) to be of the type given :

```typescript
function HasStatic<Type>() {
    return (target: Type) => {};
}
```

And this is how it has to be apply to our class :

```typescript
@HasStatic<Storable<Person, string>>()
class Person implements HasName {
    name!: string
    birthDate!: Date
    static async load(name: string): Promise<Person> {
        // get data and create a new Person someway
    }
    static async save(person: Person) {
        // save the Person someway
    }
}
```

Notice that we used a decorator factory instead of a decorator, because we wouldn't be able to use the latter like this : `@HasStatic<Storable<Person, string>>`.

--------

Thank you for reading, Typescript Padawan !