Reputation: 3711
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?
Upvotes: 228
Views: 299972
Reputation: 449
Extending on some of the earlier answers; if your class has methods, other than the constructor, that need to create instances of type T - `
class A {
s: string;
constructor(s: String){
this.s = s;
}
}
class B<T> {
TCreator: {new( s:string ): T; };
constructor(TCreator: {new( s:string ): T; }){
this.TCreator = TCreator;
}
create = (s: String):T => {
return new this.TCreator(s);
}
}
var test = new B<A>(A);
var s = test.create("blah");
//s.s == "blah"
`
Upvotes: 0
Reputation: 3277
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);
Upvotes: 0
Reputation: 779
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.
Upvotes: 3
Reputation: 249
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!
Upvotes: 0
Reputation: 119
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);
});
Upvotes: 4
Reputation: 1381
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);
Upvotes: 23
Reputation: 780
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
Upvotes: 1
Reputation: 195
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();
Upvotes: 8
Reputation: 636
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.
Upvotes: 14
Reputation: 7
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]);
}
}
Upvotes: -1
Reputation: 251262
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();
Upvotes: 124
Reputation: 22874
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
Upvotes: 221
Reputation: 501
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);
Upvotes: 50