Inheritable Static Class Methods and Overriding
Tags: oop, typescript
Published: 2020-10-05 13:01:31

I like Object oriented programming (`oop`), and inheritance, but it can be painful at times when you can't express yourself fully.

Typing things in typescript can be tricky.

I often like to use static class initializers (useful for async operations), but I've been struggling with how to inherit initializers. I figured it out and thought I'd put it somewhere for safe keeping.

```ts
class Foo {
    constructor (public props?: {
        alpha?: boolean
    }) {}
    static init <T extends typeof Foo>(this: T, props?: ConstructorParameters<T>[0]): InstanceType<T>  {
        return new this(props) as InstanceType<T>;
    }
}

class Bar extends Foo {
    constructor (public props?: NonNullable<ConstructorParameters<typeof Foo>[0]> & {
        beta?: boolean
    }) {
        super();
    }
    static init <T extends typeof Bar>(this: T, props?: ConstructorParameters<T>[0]): InstanceType<T>  {
        return new this(props) as InstanceType<T>;
    }
}

const x = Foo.init({ alpha: true })
const y = Bar.init({ alpha: true, beta: true })
```

[Playground](https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8CwAoaXrHgOwgBcAnAV2EPmOgAoAHUgIxAEtho7j46IB+ALhQZsI6GBB0AFmAHRGiEAFMweYdgC+AShTq1WImEJtoLPC0LQAPABVoigB6FFeACYxCATzqL4AMziIAHw0hJIsEILWADQcXDyyAML4RGQUVAAKYMRgALaKTsQQNoEA2gAMALqaggCSBIQqwIrWXorFWGiYoljE+aTEeNB4igDu0KHh9HEQ2pDQdQZ4TS3exQDcetC66NsYoJAwAEJZdo7ObgFInSK49amU1PRMrOyc3HyCAHL4n6Qg4Mw2kk7uQHplsnkCkVPN4-JdSpVAtAAGRCLrdRj5GSCeTwJQqTZaNHdfSkbzEGiaDborabAxGdimcxWWwOJyudytOHHYjBCYRaDRWLvRLJEigjJZXL5RSFYrlKq1eqNZqtdrEkm9Qj9QbDMb8qbvWYwBYNJaq1bWQLUkTbXboW5EaD2aAAXkuADomYQaMgxBJpIJxYotpo9mLoB43dAeV6zD6-eIpGAg2RFDFMQ1U6QQ1ogA)

Here you can see that we have `Foo` and `Bar` they both have their own `init` method, that returns their respective class instances.

We can further expand on this.

Taking this another step further, we may want to extend the parent's `init` method, and run it was well. This allows us to chain the encapsulated / replaced functionality through to the children classes.

```ts
class Foo {
    constructor (public props?: {
        alpha?: boolean
    }) {}
    dogs?: boolean;
    init () {
        this.dogs = true;
        return this;
    }
    static init <T extends typeof Foo>(this: T, props?: ConstructorParameters<T>[0]): InstanceType<T>  {
        const t = new this(props) as InstanceType<T>;
        this.init();
        return t;
    }
}

class Bar extends Foo {
    constructor (public props?: NonNullable<ConstructorParameters<typeof Foo>[0]> & {
        beta?: boolean
    }) {
        super();
    }
    cats?: boolean;
    init () {
        super.init();
        this.cats = true;
        return this;
    }
    static init <T extends typeof Bar>(this: T, props?: ConstructorParameters<T>[0]): InstanceType<T>  {
        const t = new this(props) as InstanceType<T>;
        t.init();
        return t;
    }
}

const y = Bar.init({ alpha: true, beta: true })
console.log(y.dogs);
```

[Playground](https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8CwAoaXrHgOwgBcAnAV2EPmOgAoAHUgIxAEtho7j46IB+ALhQZsI6GBB0AFmAHRGiEAFMweYdgC+AShTq1WACbwA5n0Hz4SlQG490FnhaFa2tJlFZCklhAB0hk9AAvNAkpIo2bu7EioSkxHghXhARIrqR0ERghGx2Dk4APAAq0IoAHoSKePowhACedIrwAGZwiAB8NJ7egoUANBxcPLIAwvhEZBRUAApgxGAAtjGKxBBFbQDaAAwAupqCAJIEhCrAioX1imtYru5YuEchQdB4igDuid70gxDakNCHmTwp3ODTWKVuXV89kcNE04KiMTiCUI8OgaTSGFAkBgACFZiVypVqq0kDdsPdxuRKNR6ExWOxONxTNAAHL4FmkEDgZiXUZHCbUmZzRYVFb5OoNZokjY7NrQABkQnSIkYMRkZgUylU6S0StuGVIDWIsNRaREwCyzPMljwqOhTlhetuEENyx89pNthEkJ8FsIMGCoXCXuw0Vi8Q+yVsZuwmWy7Ht0CKBIqVRqFyleOIHUhPX6jKGgj5lMmxCFCyWYsKMt2ByOJzOFyuTvcFKcTmCL3ekK+TN+MABxyBjdB1dR3vdeU9ytDiIjKOjGAx6Db0FqTyzk5hyDEEmkgiD-VVxwPZEUaM0mLGFkUPhAxhotT8xh+ViAA)

This is one trick we can add to the typescript `oop` toolbox, to fully express ourselves better.