Is there any way to instantiate a Generic literal type in typescript?

Viewed 263

I want to do something that's probably unorthodox (and borderline useless if we're being honest) so here we go:

I want to pass a literal as a Generic parameter and then instantiate it. Consider the following example:

const log = console.log;

class Root<T = {}> {
  // public y: T = {}; // this obviously doesn't work

  // again this won't work because T is used a value. Even if it worked,
  // we want to pass a literal
  // public y: T = new T();

  public x: T;
  constructor(x: T) {
    this.x = x;
  }
}

class Child extends Root<{
  name: "George",
  surname: "Typescript",
  age: 5
}> {
  constructor() {
    // Duplicate code. How can I avoid this?
    super({
      name: "George",
      surname: "Typescript",
      age: 5
    });
  }

  foo() {
    // autocomplete on x works because we gave the type as Generic parameter
    log(`${this.x.name} ${this.x.surname} ${this.x.age}`); 
  }
}


const main = () => {
  const m: Child = new Child();
  m.foo();
};
main();

This works but I have to pass the literal twice. Once on generic for autocompletion to work and once on constructor for initialization. Ugh.

One other way to do it would be to declare my literal outside of Child. Like this:

const log = console.log;

class Root<T = {}> {
  // public y: T = {}; // this obviously doesn't work

  // again this won't work because T is used a value. Even if it worked,
  // we want to pass a literal
  // public y: T = new T(); 

  public x: T;
  constructor(x: T) {
    this.x = x;
  }
}

// works but ugh..... I don't like it. I don't want to declare things outside of my class
const literal = {
  name: "George",
  surname: "Typescript",
  age: 5
}
class Child extends Root<typeof literal> {
  constructor() {
    super(literal);
  }

  foo() {
    // autocomplete on x works because we gave the type as Generic parameter
    log(`${this.x.name} ${this.x.surname} ${this.x.age}`); 
  }
}


const main = () => {
  const m: Child = new Child();
  m.foo();
};
main();

Is there any magical way to instantiate the Generic type without explicitly providing it again through a constructor?

2 Answers

You could use an intermediate wrapper that would take care of both expanding the generic and calling the constructor:

function fromRoot<T>(x: T) {
  return class extends Root<T> {
    constructor() {
      super(x)
    }
  }
}

and then:

class Child extends fromRoot({
  name: "George",
  surname: "Typescript",
  age: 5
}) { etc }

PG

You need to be aware that compiled Javascript doesn't know about generics and therefore you can't use them to create new object.

Also, I don't see the point of Child class if it'll be constrained to specific object - why don't you define type that your Child expects, and then instantiate child with specific instance of that type?

type MyType = {
    name: string
    surname: string
    age: number
}

class Child extends Root<MyType> {
    foo() {
        // autocomplete on x works because we gave the type as Generic parameter
        console.log(`${this.x.name} ${this.x.surname} ${this.x.age}`);
    }
}

const child = new Child({
    name: "George",
    surname: "Typescript",
    age: 5
})

If you want to reuse that specific Child you could just export that specific child instance.

Please see playground.

Related