Search code examples
typescripttypescript-genericsmixins

Pass generic parameter in Typescript Mixin


When using a mixin that builds up on a generic class, I need to set it's value to unknown, causing the implementing class having a generic parameter of type unknown | concrete.

I've build an example here using Angular, but it's completely Typescript related: https://stackblitz.com/edit/angular-svw6ke?file=src%2Fapp%2Fapp.component.ts

Is there any chance to redesign this mixin (using Typescript 4.4) so the type won't get malformed?


Solution

  • You need to carry the generic parameter through the mixin function. Titian's answer here gives a good explanation of how this works. For your problem, it would look something like

    export function genericMixin1<
      ModelType,
      ComponentType extends AbstractConstructor<ComponentBase<ModelType>>
    >(Base: T) {
      abstract class GenericMixin extends Base {
        //Logic here
      }
      return GenericMixin;
    }
    

    It helps to know going in that what he calls the "two call approach" stems from a workaround for a common problem with TypeScript generics -- you can't partially infer generic type parameters when calling a function, but you can sort of "divide" the generic parameters between the one you're calling and a second inner function. For example:

    export function genericMixin2<T>() {
      return function<
        ComponentType extends AbstractConstructor<ComponentBase<T>>
      >(Base: ComponentType) {
        abstract class GenericMixin extends Base {
          //Logic here
        }
        return GenericMixin;
      };
    }
    

    (ETA: Fixed, Playground example here.)

    Now instead of writing const StringMixin = genericMixin1<string, ComponentBase<string>>(ComponentBase); you can write const StringMixin = (genericMixin2<string>()(ComponentBase)). This is only a slight improvement with one generic parameter, but becomes absolutely necessary when you have many generic params. Otherwise, you wind up with const ManyParamsMixin = genericMixin<string, number, boolean, BaseThing<string,number,boolean>>(BaseThing)... it gets ugly fast.