213

I'm trying to create a new object of a type parameter in my generic class. In my class View, I have 2 lists of objects of generic type passed as type parameters, but when I try to make new TGridView(), TypeScript says:

Could not find symbol 'TGridView

This is the code:

module AppFW {
    // Represents a view
    export class View<TFormView extends FormView, TGridView extends GridView> {
        // The list of forms 
        public Forms: { [idForm: string]: TFormView; } = {};

        // The list of grids
        public Grids: { [idForm: string]: TGridView; } = {};

        public AddForm(formElement: HTMLFormElement, dataModel: any, submitFunction?: (e: SubmitFormViewEvent) => boolean): FormView {
            var newForm: TFormView = new TFormView(formElement, dataModel, submitFunction);
            this.Forms[formElement.id] = newForm;
            return newForm;
        }

        public AddGrid(element: HTMLDivElement, gridOptions: any): GridView {
            var newGrid: TGridView = new TGridView(element, gridOptions);
            this.Grids[element.id] = newGrid;
            return newGrid;
        }
    }
}

Can I create objects from a generic type?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Javier Ros
  • 3,511
  • 2
  • 21
  • 41

12 Answers12

206

To create a new object within generic code, you need to refer to the type by its constructor function. So instead of writing this:

function activatorNotWorking<T extends IActivatable>(type: T): T {
    return new T(); // compile error could not find symbol T
}

You need to write this:

function activator<T extends IActivatable>(type: { new(): T ;} ): T {
    return new type();
}

var classA: ClassA = activator(ClassA);

See this question: Generic Type Inference with Class Argument

Community
  • 1
  • 1
blorkfish
  • 21,800
  • 4
  • 33
  • 24
  • 54
    Somewhat late (but useful) comment - if your constructor takes args then the `new()` needs to as well - or a more generic: `type: {new(...args : any[]): T ;}` – Rycochet Apr 14 '16 at 08:46
  • 3
    @Yoda IActivatable is a self created interface, see other question: https://stackoverflow.com/questions/24677592/generic-type-inference-with-class-argument – Jamie Jan 01 '18 at 20:26
  • 1
    How should this be inferred `{ new(): T ;}`. I am having it hard time to learning this part. – abitcode Oct 11 '19 at 08:25
  • 3
    This (and all other solutions here) require you to pass in the class on top of specifying the generic. This is hacky and redundant. It means if I have a base class `ClassA` and extend it `ClassB extends ClassA`, I also have to pass in `new () => T = MyClass` or none of these solutions work. – AndrewBenjamin Jan 10 '20 at 19:06
  • 2
    @AndrewBenjamin What is your suggestion then? Not trying to start anything (actually want to learn) but calling it hacky and redundant infers you know of another way that you aren't sharing :) – perry Feb 25 '20 at 23:14
  • I totally agree with @AndrewBenjamin that it's inelegant because the caller must pass in the class twice, but it seems to be the best that Typescript can manage at the moment. Can anyone explain the syntax "type: { new(): T ;} " and whether the semicolon serves any purpose? – Andy Mar 30 '20 at 15:53
  • @perry 4 months later, I happened upon my solution. What I needed was my class `MyClass` to activate the constructor of `T` by newing it up. To do this, I passed the type once to MyClass's constructor `protected type: new (...args: any[]) => T`, where ...args are my constructor arguments for T. Then when I need an instance I go `new this.type(obj)`, where obj is `Partial` from my database query. I put the bulk of this logic in a base class so I only have to remember to extend the base class and pass in the type of T once to the constructor. Not ideal, definitely hacky. – AndrewBenjamin Apr 13 '20 at 05:26
  • @AndrewBenjamin If you have a better way, can you add it as answer that shows concrete examples? It's hard to follow a block of text. – claudekennilol Jul 14 '20 at 15:51
  • @claudekennilol okay I will try. see my solution below or above or wherever it ends up. – AndrewBenjamin Jul 15 '20 at 00:25
  • All works fine until you pass in another generic type for T. The new type() seems to give way. I'm getting Value of type 'typeof MyPassedInGenericClassType' is not callable. Did you mean to include 'new'? – Ε Г И І И О Aug 07 '21 at 07:18
121

Because the compiled JavaScript has all the type information erased, you can't use T to new up an object.

You can do this in a non-generic way by passing the type into the constructor.

class TestOne {
    hi() {
        alert('Hi');
    }
}

class TestTwo {
    constructor(private testType) {

    }
    getNew() {
        return new this.testType();
    }
}

var test = new TestTwo(TestOne);

var example = test.getNew();
example.hi();

You could extend this example using generics to tighten up the types:

class TestBase {
    hi() {
        alert('Hi from base');
    }
}

class TestSub extends TestBase {
    hi() {
        alert('Hi from sub');
    }
}

class TestTwo<T extends TestBase> {
    constructor(private testType: new () => T) {
    }

    getNew() : T {
        return new this.testType();
    }
}

//var test = new TestTwo<TestBase>(TestBase);
var test = new TestTwo<TestSub>(TestSub);

var example = test.getNew();
example.hi();
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 3
    This doesn't work for Angular 11 with Ivy enabled, do you have any solution for that? The error message is the following: _This type is not supported as injection token. NG2003: No suitable injection token for parameter 'type' of class 'Component'. Consider using the @Inject decorator to specify an injection token._ – Ragnar Apr 29 '21 at 04:31
49

All type information is erased in JavaScript side and therefore you can't new up T just like @Sohnee states, but I would prefer having typed parameter passed in to constructor:

class A {
}

class B<T> {
    Prop: T;
    constructor(TCreator: { new (): T; }) {
        this.Prop = new TCreator();
    }
}

var test = new B<A>(A);
wonea
  • 4,783
  • 17
  • 86
  • 139
TadasPa
  • 491
  • 4
  • 3
21

I know late but @TadasPa's answer can be adjusted a little by using

TCreator: new() => T

instead of

TCreator: { new (): T; }

so the result should look like this

class A {
}

class B<T> {
    Prop: T;
    constructor(TCreator: new() => T) {
        this.Prop = new TCreator();
    }
}

var test = new B<A>(A);
Jazib
  • 1,343
  • 13
  • 27
13
export abstract class formBase<T> extends baseClass {

  protected item = {} as T;
}

Its object will be able to receive any parameter, however, type T is only a typescript reference and can not be created through a constructor. That is, it will not create any class objects.

jales cardoso
  • 591
  • 7
  • 11
  • 11
    *Beware*, this may work but the resulting object wont be of Type `T`, so any getters/setters defined in `T` may not work. – Daniel Ormeño Jul 05 '17 at 00:49
  • @DanielOrmeño care to explain? What do you mean by that? My code seems to be working using this snippet. Thanks. – eestein Jul 18 '17 at 15:55
  • Javascript is an interpreted language and ecmascript does not yet have references to types or classes. So T is just a reference for typescript. – jales cardoso Aug 17 '17 at 15:58
  • 1
    This is a WRONG answer, even in JavaScript. If you have a `class Foo{ method() { return true; }`, casting {} to T will not give you this method! – JCKödel Apr 26 '19 at 16:12
8

I was trying to instantiate the generic from within a base class. None of the above examples worked for me as they required a concrete type in order to call the factory method.

After researching for awhile on this and unable to find a solution online, I discovered that this appears to work.

 protected activeRow: T = {} as T;

The pieces:

 activeRow: T = {} <-- activeRow now equals a new object...

...

 as T; <-- As the type I specified. 

All together

 export abstract class GridRowEditDialogBase<T extends DataRow> extends DialogBase{ 
      protected activeRow: T = {} as T;
 }

That said, if you need an actual instance you should use:

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


export class Foo {
  bar() {
    console.log("Hello World")
  }
}
getInstance(Foo).bar();

If you have arguments, you can use.

export class Foo2 {
  constructor(public arg1: string, public arg2: number) {

  }

  bar() {
    console.log(this.arg1);
    console.log(this.arg2);
  }
}
getInstance(Foo, "Hello World", 2).bar();
  • 3
    This is a WRONG answer, even in JavaScript. If you have a class Foo{ method() { return true; }, casting {} to T will not give you this method! – JCKödel Apr 26 '19 at 16:12
  • I works if you do not have methods. If you however have an object with methods you must used the code I just added. – Christian Patzer May 10 '19 at 19:35
3

I'm adding this by request, not because I think it directly solves the question. My solution involves a table component for displaying tables from my SQL database:

export class TableComponent<T> {

    public Data: T[] = [];

    public constructor(
        protected type: new (value: Partial<T>) => T
    ) { }

    protected insertRow(value: Partial<T>): void {
        let row: T = new this.type(value);
        this.Data.push(row);
    }
}

To put this to use, assume I have a view (or table) in my database VW_MyData and I want to hit the constructor of my VW_MyData class for every entry returned from a query:

export class MyDataComponent extends TableComponent<VW_MyData> {

    public constructor(protected service: DataService) {
        super(VW_MyData);
        this.query();
    }

    protected query(): void {
        this.service.post(...).subscribe((json: VW_MyData[]) => {
            for (let item of json) {
                this.insertRow(item);
            }
        }
    }
}

The reason this is desirable over simply assigning the returned value to Data, is say I have some code that applies a transformation to some column of VW_MyData in its constructor:

export class VW_MyData {
    
    public RawColumn: string;
    public TransformedColumn: string;


    public constructor(init?: Partial<VW_MyData>) {
        Object.assign(this, init);
        this.TransformedColumn = this.transform(this.RawColumn);
    }

    protected transform(input: string): string {
        return `Transformation of ${input}!`;
    }
}

This allows me to perform transformations, validations, and whatever else on all my data coming in to TypeScript. Hopefully it provides some insight for someone.

AndrewBenjamin
  • 651
  • 1
  • 7
  • 16
2

This is what I do to retain type info:

class Helper {
   public static createRaw<T>(TCreator: { new (): T; }, data: any): T
   {
     return Object.assign(new TCreator(), data);
   }
   public static create<T>(TCreator: { new (): T; }, data: T): T
   {
      return this.createRaw(TCreator, data);
   }
}

...

it('create helper', () => {
    class A {
        public data: string;
    }
    class B {
        public data: string;
        public getData(): string {
            return this.data;
        }
    }
    var str = "foobar";

    var a1 = Helper.create<A>(A, {data: str});
    expect(a1 instanceof A).toBeTruthy();
    expect(a1.data).toBe(str);

    var a2 = Helper.create(A, {data: str});
    expect(a2 instanceof A).toBeTruthy();
    expect(a2.data).toBe(str);

    var b1 = Helper.createRaw(B, {data: str});
    expect(b1 instanceof B).toBeTruthy();
    expect(b1.data).toBe(str);
    expect(b1.getData()).toBe(str);

});
Saturnus
  • 99
  • 6
1

Not quite answering the question, but, there is a nice library for those kind of problems: https://github.com/typestack/class-transformer (although it won't work for generic types, as they don't really exists at run-time (here all work is done with class names (which are classes constructors)))

For instance:

import {Type, plainToClass, deserialize} from "class-transformer";

export class Foo
{
    @Type(Bar)
    public nestedClass: Bar;

    public someVar: string;

    public someMethod(): string
    {
        return this.nestedClass.someVar + this.someVar;
    }
}

export class Bar
{
    public someVar: string;
}

const json = '{"someVar": "a", "nestedClass": {"someVar": "B"}}';
const optionA = plainToClass(Foo, JSON.parse(json));
const optionB = deserialize(Foo, json);

optionA.someMethod(); // works
optionB.someMethod(); // works
JCKödel
  • 723
  • 9
  • 19
1

I'm late for the party but this is the way I got it working. For arrays we need do some tricks:

   public clone<T>(sourceObj: T): T {
      var cloneObj: T = {} as T;
      for (var key in sourceObj) {
         if (sourceObj[key] instanceof Array) {
            if (sourceObj[key]) {
               // create an empty value first
               let str: string = '{"' + key + '" : ""}';
               Object.assign(cloneObj, JSON.parse(str))
               // update with the real value
               cloneObj[key] = sourceObj[key];
            } else {
               Object.assign(cloneObj, [])
            }
         } else if (typeof sourceObj[key] === "object") {
            cloneObj[key] = this.clone(sourceObj[key]);
         } else {
            if (cloneObj.hasOwnProperty(key)) {
               cloneObj[key] = sourceObj[key];
            } else { // insert the property
               // need create a JSON to use the 'key' as its value
               let str: string = '{"' + key + '" : "' + sourceObj[key] + '"}';
               // insert the new field
               Object.assign(cloneObj, JSON.parse(str))
            }
         }
      }
      return cloneObj;
   }

Use it like this:

  let newObj: SomeClass = clone<SomeClass>(someClassObj);

It can be improved but worked for my needs!

EQuadrado
  • 221
  • 4
  • 4
0

i use this: let instance = <T>{}; it generally works EDIT 1:

export class EntityCollection<T extends { id: number }>{
  mutable: EditableEntity<T>[] = [];
  immutable: T[] = [];
  edit(index: number) {
    this.mutable[index].entity = Object.assign(<T>{}, this.immutable[index]);
  }
}
Bojo
  • 9
  • 2
  • 5
    This won't actually instantiate a new instance of `T`, it will just make an empty object that TypeScript *thinks* is of type `T`. You may want to explain your answer in a bit more detail. – Sandy Gifford Oct 03 '17 at 15:32
  • I said it generally works. It is exactly as you say. Compiler is not whining and you have generic code. You can use this instance to manually set properties you want without the need of explicit constructor. – Bojo Oct 04 '17 at 08:55
0

Here is example if you need parameters in constructor:

class Sample {
    public innerField: string;

    constructor(data: Partial<Sample>) {
        this.innerField = data.innerField;
    }
}

export class GenericWithParams<TType> {
    public innerItem: TType;

    constructor(data: Partial<GenericWithParams<TType>>, private typePrototype: new (i: Partial<TType>) => TType) {
        this.innerItem = this.factoryMethodOnModel(data.innerItem);
    }

    private factoryMethodOnModel = (item: Partial<TType>): TType => {
        return new this.typePrototype(item);
    };
}

const instance = new GenericWithParams<Sample>({ innerItem : { innerField: 'test' }}, Sample);
0lukasz0
  • 3,155
  • 1
  • 24
  • 40