2

Need to create some factory method inside Angular 5 service based on generic type T passed to this service. How to get name of generic type "T"?

@Injectable()
export class SomeService<T> {

    someModel: T;

    constructor(protected userService: UserService) {

        let user = this.userService.getLocalUser();
        let type: new () => T;

        console.log(typeof(type)) // returns "undefined"
        console.log(type instanceof MyModel) // returns "false"

        let model = new T(); // doesn't compile, T refers to a type, but used as a value

        // I also tried to initialize type, but compiler says that types are different and can't be assigned

        let type: new () => T = {}; // doesn't compile, {} is not assignable to type T 
    }
}

// This is how this service is supposed to be initialized

class SomeComponent {

    constructor(service: SomeService<MyModel>) {
        let modelName = this.service.getSomeInfoAboutInternalModel();
    }
}
Anonymous
  • 1,823
  • 2
  • 35
  • 74
  • 1
    Maybe [instanceof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) is what you're looking for – Sam Herrmann Mar 21 '18 at 19:01
  • Possible duplicate of [Is there a way to get the name of the generic type in a generic () function/class (Typescript)?](https://stackoverflow.com/questions/36558088/is-there-a-way-to-get-the-name-of-the-generic-type-in-a-generic-t-function-c) – Igor Mar 21 '18 at 19:04
  • 1
    `console.log(typeof(type))` returns undefined because `type` is a non-initialised variable. You must remember that TypeScript's type annotations disappear at runtime, so, during runtime, you're doing this: `console.log(typeof(undefined))` which, of course, returns `undefined`. – Oscar Paz Mar 21 '18 at 19:38
  • @Igor 1. that question was not solved, 2. the only answer it has suggests to pass type as a parameter, if I wanted to do this, I would do this even simpler by creating method setType(DynamicType) { return new DynamicType() } but I'm using constructor, moreover, constructor of the Angular service that cannot be initialized manually – Anonymous Mar 21 '18 at 20:06
  • @Sam Herrmann when I use this service this way SomeService then inside of the service "type instanceof MyModel" always returns "false" – Anonymous Mar 21 '18 at 20:10
  • @Oscar Paz can I create some dummy instance inside my constructor based on type T? – Anonymous Mar 21 '18 at 20:18
  • 1
    Possible duplicate of [Get type of generic parameter](https://stackoverflow.com/a/18216538/1260204) – Igor Mar 21 '18 at 20:23
  • 1
    I proposed the duplicates because they are the same. There is no answer because without an instance it is not possible to do at run time. – Igor Mar 21 '18 at 20:25

2 Answers2

6

You cannot instantiate a class based on generic types only.

I mean, if you want this:

function createInstance<T>(): T {...}

It is not possible, because, it would transpile into this:

function createInstance() {...}

Which, as you can see, cannot be parametrized in any way.

The closest you can get to what you want is this:

function createInstance<T>(type: new() => T): T {
    return new type();
}

Then, if you have a class with a parameterless constructor:

class C1 {
   name: string;
   constructor() { name = 'my name'; }
}

You can now do this:

createInstance(C1); // returns an object <C1>{ name: 'my name' }

This works perfectly and the compiler gives you correct type information. The reason I'm using new() => T as the type for type, is to indicate that you must pass a constructor function with no parameters that must return a type T. The class itself is exactly that. In this case, if you have

class C2 {
    constructor(private name: string) {}
}

And you do

createInstance(C2);

the compiler will throw an error.

You can, however, generalise the createInstance function so it works for objects with any number of parameters:

function createInstance2<T>(type: new (...args) => T, ...args: any[]): T 
{
    return new type(...args);
}

Now:

createInstance(C1); // returns <C1>{ name: 'my name'}
createInstance(C2, 'John'); // returns <C2>{name: 'John'}

I hope this serves you.

Oscar Paz
  • 18,084
  • 3
  • 27
  • 42
1

Genrics are used for type validation

class Array<T>{
  pop:() => T;
  unshift:(v:T) => void;
}

let numbers: Array<number> = ['1212']; //error
let strings: Array<string> = ['1','2','3']; //work


class Product{

}

let products: Array<Product> = [new Product(), new Product()]; //works
Vayrex
  • 1,417
  • 1
  • 11
  • 13
  • This check is done outside of the Array class and it's done on a syntax level, how to do this check inside of Array class? Is it possible? – Anonymous Mar 21 '18 at 20:13
  • 1
    Seems you don't understand generics. If you have some class or method which is doing the same for any input (and it is able to do it), and you want compiler to validate output. Then you are having generic functionality. T is just for compiler not for code. It can be a class which implements some interface or extends some abstract class. – Vayrex Mar 21 '18 at 20:22
  • 1
    In other words class Test{ T here is not a instance or variable, you can not log it to console or use in code, just in input/output types } – Vayrex Mar 21 '18 at 20:30