How to extend class with generics combined with mixins in Angular?

Viewed 399

Background

I want to create the following structure (blue are abstract and green are concrete):

enter image description here

So I followed this answer and created the following code:

export abstract class BaseResourceModel {
    public id?: string;
}

type ConcreteClass<C> = new (...args: any[]) => C;

export interface IBaseList<T extends BaseResourceModel>
  extends ConcreteClass<BaseList<T>> {
}

export function TableMixin<T extends BaseResourceModel>() {
  return function <B extends IBaseList<T>>(Base: B){
    class Temporary extends Base implements OnInit {
       (...)
    }
    return Temporary;
  };
}

@Component({ template: '' })
export abstract class BaseList<T extends BaseResourceModel>
  implements OnInit, OnDestroy {
  (...) 
}

@Component({ template: '' })
export abstract class BaseServerList<T extends BaseResourceModel> extends BaseList<T> {
  (...) 
}

Problem

I don't know exactly how to create the abstract BaseServerTable extended from BaseServerList and composed with TableMixin keeping the generic <T extends BaseResourceModel>on it. I get the following errors:

  • First attempt:
class DummyBaseServerList<T> extends BaseServerList<T> { }

@Component({ template: '' })
class BaseServerTable<T> extends TableMixin<BaseResourceModel, typeof DummyBaseServerList>()(BaseServerList<T>) {
  (...)
}

Results in: Expected 1 type arguments, but got 2.ts(2558)

  • Second attempt:
class DummyBaseServerList<T> extends BaseServerList<T> { }

@Component({ template: '' })
class BaseServerTable extends TableMixin<BaseResourceModel>()(DummyBaseServerList) {
  (...)
}

Results in: No base constructor has the specified number of type arguments.ts(2508)

  • Third attempt:
@Component({ template: '' })
class BaseServerTable<T> extends TableMixin<BaseResourceModel>()(BaseServerList<T>) {
  (...)
}

Results in: Value of type 'typeof BaseServerList' is not callable. Did you mean to include 'new'?ts(2348)

Question

How to implement this pattern? What I am missing? I've searched everywhere about samples a pattern like this

1 Answers

I think the syntax you want is this:

class BaseServerTable<T> extends
    (TableMixin<BaseResourceModel>()(DummyBaseList))<T> { } // okay

TableMixin is a generic function with exactly one type parameter and no function parameters. So you have to call it like TableMixin<BaseResourceModel>(). That results in:

const TMBRM = TableMixin<BaseResourceModel>();
/* const TMBRM: <B extends IBaseList<BaseResourceModel>>(Base: B) => {
    new (...args: any[]): TableMixin<BaseResourceModel>.(Anonymous function)<B>.Temporary;
    prototype: TableMixin<...>.(Anonymous function)<...>.Temporary;
} & B */

which is a function that takes a class constructor and returns another class constructor. So you can give it DummyBaseList (and not anything like DummyBaseList<T>):

const TMBRMDBL = TMBRM(DummyBaseList);
/*const TMBRMDBL: {
    new (...args: any[]): TableMixin<BaseResourceModel>.
       (Anonymous function)<typeof DummyBaseList>.Temporary;
    prototype: TableMixin<...>.(Anonymous function)<...>.Temporary;
} & typeof DummyBaseList */

So that is a class constructor. It seems to be a generic class constructor because its type is also assignable typeof DummyBaseList, so we should be able to extend it with a type parameter:

class BST<T> extends TMBRMDBL<T> { } // okay

It's a bit of a tricky thing in TypeScript; normally a generic class constructor value (like Array) cannot be given a generic type parameter (saying const foo = Array<string> is an error); it's only the generic class interface type that can do that (so const bar: Array<string> is okay). But when you extend a generic class, you do put a type parameter after the constructor name (like class Foo extends Array<string> { }.


So that's how you get the code to compile without syntax errors.

Note that I have absolutely no idea if this behaves how you want, since it's full of angular-specific and other functionality that I'm not an expert in. For all I know, the next thing you try to do after this will break horribly... but for the question as asked, that's the answer.

Playground link to code

Related