208 votes

Créer un nouvel objet à partir d'un paramètre de type dans une classe générique

J'essaie de créer un nouvel objet d'un paramètre de type dans ma classe générique. Dans ma classe View J'ai 2 listes d'objets de type générique passés comme paramètres de type, mais quand j'essaie de faire de la new TGridView() TypeScript dit :

Impossible de trouver le symbole 'TGridView

Voici le 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;
        }
    }
}

Puis-je créer des objets à partir d'un type générique ?

8voto

Christian Patzer Points 157

J'essayais d'instancier le générique à partir d'une classe de base. Aucun des exemples ci-dessus n'a fonctionné pour moi, car ils nécessitaient un type concret afin d'appeler la méthode de fabrique.

Après avoir fait des recherches pendant un certain temps sur ce sujet et ne pas avoir trouvé de solution en ligne, j'ai découvert que ceci semble fonctionner.

 protected activeRow: T = {} as T;

Les pièces :

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

...

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

Tous ensemble

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

Cela dit, si vous avez besoin d'une instance réelle, vous devez utiliser :

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();

Si vous avez des arguments, vous pouvez utiliser.

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();

3voto

AndrewBenjamin Points 347

Je l'ajoute à la demande, et non parce que je pense que cela résout directement la question. Ma solution implique un composant de table pour afficher les tables de ma base de données SQL :

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);
    }
}

Pour mettre cela en pratique, supposons que j'ai une vue (ou une table) dans ma base de données VW_MyData et que je veux lancer le constructeur de ma classe VW_MyData pour chaque entrée renvoyée par une requête :

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);
            }
        }
    }
}

La raison pour laquelle cela est préférable à la simple affectation de la valeur renvoyée à Data est la suivante : disons que j'ai un code qui applique une transformation à une colonne de VW_MyData dans son constructeur :

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}!`;
    }
}

Cela me permet d'effectuer des transformations, des validations et autres sur toutes mes données entrant dans TypeScript. J'espère que cela donnera un aperçu à quelqu'un.

2voto

Saturnus Points 57

C'est ce que je fais pour retenir les informations de type :

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);

});

1voto

JCKödel Points 173

Je ne réponds pas vraiment à la question, mais il existe une bibliothèque intéressante pour ce genre de problèmes : https://github.com/typestack/class-transformer (bien que cela ne fonctionne pas pour les types génériques, car ils n'existent pas vraiment au moment de l'exécution (ici, tout le travail est fait avec les noms de classe (qui sont des constructeurs de classes)))

Par exemple :

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

1voto

EQuadrado Points 91

Je suis en retard pour la fête mais c'est ainsi que j'ai réussi à le faire fonctionner. Pour les tableaux, nous avons besoin de quelques astuces :

   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;
   }

Utilisez-le comme ça :

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

Il peut être amélioré mais il répondait à mes besoins !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X