Creating TypeScript objects dynamically - javascript

Following a post regarding creating objects dynamically in TypeScript, I have the following code used as a factory to create an object from its name:
public createComponent(context: Object, componentName: string): ComponentRef<ComponentBase> {
this.viewContainer.clear();
var instance =
Object.create(context[componentName].prototype); // <-- fails
let componentFactory =
this.componentFactoryResolver.resolveComponentFactory(instance);
return <ComponentRef<ComponentBase>> this.viewContainer.createComponent(componentFactory);
}
I'm not entirely convinced I understand this window[suchAndSuch] syntax: what does it mean? Can't find any documentation for it.
In any event it seems that window[myClassName] is undefined, even though the class in question is defined in the same file. I have looked at the Javascript transpiled from the TypeScript, and the class in question is in scope.
I think.
Help!
-- UPDATE --
I have this code, which is part of an Angular 2 application, that is calling the above method to try to get an instance of a component, injectables and all:
export class CustomUserComponent implements OnChanges {
#Input() componentType: string;
#ViewChild(ComponentDirective) componentAnchor: ComponentDirective;
ref: ComponentRef<GalleriaComponentBase>;
ngAfterViewInit() {
this.ref = this.componentAnchor
.createComponent(window, this.componentType);
}
}

Related

Cannot use "interface" declarations from TypeScript in JS (React Native tests)

I have a ReactNative app and I am trying to write a test using Jest. My test need to use classes from a native component (react-native-nfc-manager) and a class I need is declared like this
export interface TagEvent {
ndefMessage: NdefRecord[];
maxSize?: number;
type?: string;
techTypes?: string[];
id?: number[];
}
and if I try to use this definition directly like
const event = new TagEvent();
I get this error
TypeError: _reactNativeNfcManager.default is not a constructor
I am coming from Java worlds so I think to myself - of course it cannot instantiate an interface, it needs a class so I write a test instance for this interface:
class TestTagEvent implements TagEvent {
ndefMessage: NdefRecord[] = []
maxSize?: number;
type?: string;
techTypes?: string[];
id?: number[];
}
But the problem seem to be different since it does not work either:
TypeError: _TestTagEvent.TestTagEvent is not a constructor
What am I missing?
You can't construct an object using an interface. An interface is just a set of properties that can be used to define required/available properties of variables.
To use the interface to initialize a variable, you want to do this:
const eventVar: TagEvent;
If you want to use TagEvent as a instantiable object, you need to define it as a class, not an interface. Something like this, where TagEventProps is the TagEvent interface you defined:
class TagEvent {
constructor(props: TagEventProps) {
this.ndefMesssage = props.nDefMessage;
//etc...
}
}
After further experiments problem has been solved by adding 'export' to the class. Admittedly, the original error description is absolutely not matching the root cause.
export class TestTagEvent implements TagEvent {

Mobx - Object literal may only specify known properties

I recently started to learn how to use Mobx to manage my application's state and recently I came across the following error:
Object literal may only specify known properties, and "data" does not exist in type "AnnotatiosMap<this, never>".
This happens whenever I want to make a property of my class private. However, if it is public or protected, the problem does not occur.
This is a small snippet of my code:
import { makeObservable, observable } from "mobx";
class Base {
private data: string[];
constructor() {
this.data = [];
makeObservable(this, {
data: observable,
});
}
public getData = (): string[] => {
return this.data;
};
}
export default new Base();
What should I do to make my property private but still being watched?
Have a great day!
From the docs:
By default TypeScript will not allow you to annotate private fields. This can be overcome by explicitly passing the relevant private fields as generic argument, like this: makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable })

Using class object from diffrent class

I’m having type script class which expose some service list
I’ve defined a property that should have reference to the ServiceCatalog class like following:
export default class myGenerator extends Generator {
private svcat: ServiceCatalog | undefined;
// and here I’ve initilzied the property
await this.getSvc();
// here I created some function the return service instances
private async getSvc() {
this.svcat = new ServiceCatalog();
await this.svcat.getServiceInstances();
}
// and here I’ve additional code which use it
this.svcat.values ….
My question is there Is a better/cleaner way of doing the in javascript/typescript ?
maybe not using the this keyword...
And also maybe a better code for testing (unit-test) ...
The way you are doing today, it is very hard to test. Why is that? Well, because if you want to isolate your Generator class from your ServiceCatalog, you will have a hard time.
What I suggest, like the colleague above, is to have the ServiceCatalog coming by customer BUT have a default value.
class MyGenerator extends Generator {
constructor(private svCat: ServiceCatalog = new ServiceCatalog()) {
super();
}
}
This way you can use it normally like
new MyGenerator()
or for testing
new MyGenerator(myFakeServiceCatalog)
Inject the Service into your myGenerator class.
Add this to your constructor:
constructor(private svcat:ServiceCatalog) {}
You can now access the injected Service using
await this.svcat.getServiceInstances();
There is no need to add a property (your svcat:ServiceCatalog|undefined part) for the service.
"this" is needed a lot in java/type-script since it refers to the current class.

typing variable when using subclasses in TypeScript (RxJS)

I have a problem while writing tests for Angular using RxJS.
I have a variable which is used as a mocking provider (actions$) which is typed as "Observable". Now I assign an instance of a subclass to it (ReplaySubject). But now the method "next" is unknown (at least in Typescript) because it's not provided by "Observable" as it is from one of the subclasses "Subject".
How can I type my variable "actions$" correctly or how can I cast correctly to fix the error message?
online example: https://ngrx.io/guide/effects/testing
see line 12 vs 41
RxJS implementation
export declare class ReplaySubject<T> extends Subject<T> {
...
}
export declare class Subject<T> extends Observable<T> implements SubscriptionLike {
...
next(value?: T): void;
...
}
my code
let actions$: Observable<any>;
actions$ = new ReplaySubject(1);
// Property 'next' does not exist on type 'Observable<any>'.
actions$.next(new someThing());
You should type actions$ as Subject<any> or—if you want even more specificity—ReplySubject<any>. Subject extends Observable, so it will work for hot and cold functions if you're using jasmine-marbles.
As for the documentation you referenced, I suspect the actions being typed as Observable is a mistake in print.

Typescript & Angular 2 Reflection

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/common';
import {ControlGroup, Control, FormBuilder, FORM_DIRECTIVES} from 'angular2/common'
#Component({
selector: 'stone-details',
templateUrl: '../stones/stone-details.component.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 commented line
let Stone = new Stone('', '');
the component 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 component isn't directly visible under the window component, 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).
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 component 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 component and you will have multiple classes where the actual class is selected by a kind property of the component class. You may want to consider the following pattern rather.
component-field-kinds.ts
export class Stone { ... }
export class Widget { ... }
export class Gizmo { ... }
generic-component.ts
import * as kinds from './component-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.

Categories

Resources