javascript - Typescript & Angular 2 Reflection - Stack Overflow

admin2025-04-21  0

Though this topic has already been discussed in other posts like this:

Dynamically loading a typescript class (reflection for typescript)

I'm not able to find an answer to my specific issue. So, pardon me if this is duplicated.

I'm trying to create a very simple directive in Angular 2 (using Typescript), which allows dynamic addition or removal of a set of controls represented by a Type. For example, if the type is:

class Stone{
  constructor(
  public nameOfStone?: string,
  public typeOfStone?: string
  ){}
}

the UI would have something like this:

I'm able to get this working with a specific Type (ex: Stone). But, given that the directive's objective is just to add this dynamic add/remove feature, I felt that it would make sense to parameterise the type to be created and use this for different type definitions. I tried something like this in the Component class:

import {Component} from 'angular2/core';
import {NgForm}    from 'angular2/mon';
import {ControlGroup, Control, FormBuilder, FORM_DIRECTIVES} from    'angular2/mon'
@Component({
  selector: 'stone-details',
  templateUrl: '../stones/stone-detailsponent.html',
  directives: [FORM_DIRECTIVES]
})
export class StoneComponent {
  type = 'Stone';
  Stones = new Array<Stone>();

  addBtnClicked(){
    let Stone = Object.create(window['Stone'].prototype);
    //let Stone = new Stone('', '');
    this.Stones.push(Stone);
  }
  removeBtnClicked(index: number){
    if(index >= this.Stones.length){
      alert('Not a valid index');
    }else if(confirm('Remove this Stone?')){
      this.Stones.splice(index, 1);
    }
  }
}

class Stone{
  constructor(
   public nameOfDeity?: string,
   public typeOfDeity?: string
  ){}
}

When I use the mented line let Stone = new Stone('', ''); the ponent works perfectly, but if I use let Stone = Object.create(window['Stone'].prototype); it doesn't seem to work and the error I see is angular2.dev.js:23941 ORIGINAL EXCEPTION: TypeError: Cannot read property 'prototype' of undefined.

I initially thought exporting the Stone class would help, but none of the crazy variations (exporting the class, trying to refer to the class as window['StoneComponent'].export_1['Stone']) helped. I understand the ponent isn't directly visible under the window ponent, but I'm not sure what I'm missing. Is there an alternate way to doing this? Am I missing something? Please advise.

P.S: I'm using the latest version of Angular 2 and Typescript (I started this application a couple of days back).

Though this topic has already been discussed in other posts like this:

Dynamically loading a typescript class (reflection for typescript)

I'm not able to find an answer to my specific issue. So, pardon me if this is duplicated.

I'm trying to create a very simple directive in Angular 2 (using Typescript), which allows dynamic addition or removal of a set of controls represented by a Type. For example, if the type is:

class Stone{
  constructor(
  public nameOfStone?: string,
  public typeOfStone?: string
  ){}
}

the UI would have something like this:

I'm able to get this working with a specific Type (ex: Stone). But, given that the directive's objective is just to add this dynamic add/remove feature, I felt that it would make sense to parameterise the type to be created and use this for different type definitions. I tried something like this in the Component class:

import {Component} from 'angular2/core';
import {NgForm}    from 'angular2/mon';
import {ControlGroup, Control, FormBuilder, FORM_DIRECTIVES} from    'angular2/mon'
@Component({
  selector: 'stone-details',
  templateUrl: '../stones/stone-details.ponent.html',
  directives: [FORM_DIRECTIVES]
})
export class StoneComponent {
  type = 'Stone';
  Stones = new Array<Stone>();

  addBtnClicked(){
    let Stone = Object.create(window['Stone'].prototype);
    //let Stone = new Stone('', '');
    this.Stones.push(Stone);
  }
  removeBtnClicked(index: number){
    if(index >= this.Stones.length){
      alert('Not a valid index');
    }else if(confirm('Remove this Stone?')){
      this.Stones.splice(index, 1);
    }
  }
}

class Stone{
  constructor(
   public nameOfDeity?: string,
   public typeOfDeity?: string
  ){}
}

When I use the mented line let Stone = new Stone('', ''); the ponent works perfectly, but if I use let Stone = Object.create(window['Stone'].prototype); it doesn't seem to work and the error I see is angular2.dev.js:23941 ORIGINAL EXCEPTION: TypeError: Cannot read property 'prototype' of undefined.

I initially thought exporting the Stone class would help, but none of the crazy variations (exporting the class, trying to refer to the class as window['StoneComponent'].export_1['Stone']) helped. I understand the ponent isn't directly visible under the window ponent, but I'm not sure what I'm missing. Is there an alternate way to doing this? Am I missing something? Please advise.

P.S: I'm using the latest version of Angular 2 and Typescript (I started this application a couple of days back).

Share Improve this question edited May 23, 2017 at 12:17 CommunityBot 11 silver badge asked Apr 15, 2016 at 2:09 user1452030user1452030 1,0513 gold badges11 silver badges22 bronze badges 7
  • does Object.create(Stone.prototype) not work? – jpopesculian Commented Apr 15, 2016 at 2:16
  • It does, but I'm not able to make it dynamic. That is, Object.create(Stone.prototype) works, which is not much different than let Stone = new Stone(); But I'm not able to use a variable in place of the type Stone, ex: Object.create(<type name variable>.prototype) – user1452030 Commented Apr 15, 2016 at 2:19
  • It's tough because its hard to know what the parent object is. Its not window I guess. Have you tried this? Otherwise you can make your own dictionary of possible prototypes to be created and then access the prototypes through that dictionary (seems safe enough, you just have to maintain it). Otherwise maybe try eval("Stone")? Feels super dirty to me though – jpopesculian Commented Apr 15, 2016 at 2:41
  • this didn't work, but eval does. But like you said, it feels very dirty. – user1452030 Commented Apr 15, 2016 at 2:46
  • 1 I would try making your own dictionary object then, that seems to me like the best solution. – jpopesculian Commented Apr 15, 2016 at 2:48
 |  Show 2 more ments

2 Answers 2

Reset to default 2

The problem with your code is definition order.

Specifically, class definitions are not hoisted like function definitions are. The tricky part is that the type Stone is hoisted, which is perfectly valid, but the value Stone, the constructor function, is not.

To get around this just move the definition of Stone above the ponent or extract it into a separate module and import it.

Do not try to shove it into a global variable, say window. That is a very poor practice and will lead to bugs and name collisions faster than one might think. It also defeats the benefits of modules.

In short, what you need is

class Stone {
  constructor(
    readonly nameOfDeity?: string,
    readonly typeOfDeity?: string
  ) {}
}

export class StoneComponent {
  kind = 'Stone';
  stones: Stone[] = [];

  addBtnClicked() {
    const stone = new Stone();
    this.stones.push(stone);
  }
  removeBtnClicked(index: number) {
    if (index >= this.stones.length) {
      alert('Not a valid index');
    } else if (confirm('Remove this Stone?')){
      this.stones.splice(index, 1);
    }
  }
}

UPDATE Since in the original question you state that this will be a generic ponent and you will have multiple classes where the actual class is selected by a kind property of the ponent class. You may want to consider the following pattern rather.

ponent-field-kinds.ts

export class Stone { ... }

export class Widget { ... }

export class Gizmo { ... }

generic-ponent.ts

import * as kinds from './ponent-field-kinds';

type Kind = keyof typeof kinds;

export class GenericComponent {
  @Input() kind: Kind;

  values: typeof kinds[Kind][] = [];

  addBtnClicked() {
    const value = new kinds[this.kind]();
    this.values.push(value);
}

Note, for what it is worth, JavaScript, and therefore TypeScript has no such thing as a dynamic class loader. This is just how the language works all the time and the whole structure is first class.

This is not Java.

Since class is simple function you could create instance using new func(), but you still have to pass func from outer code.

I guess the most efficient solution is the following:

export class StoneComponent<T> {
  addBtnClicked(){
    let item:T = <T>{}
    this.items.push(item);
  }
}

types will match as long as objects have the same set of properties.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745241903a292060.html

最新回复(0)