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).
Object.create(Stone.prototype)
not work?
– jpopesculian
Commented
Apr 15, 2016 at 2:16
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
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.