I have two classes where I want to define a static Schema property using typebox. I also need to define a dependency between the two classes: first class schema must have an array of type of the second class schema:
import {Type, Static} from '#sinclair/typebox';
class ClassA {
static Schema = Type.Object({
id: Type.String(),
data: Type.Array(ClassB.Schema) // ERROR: <-- Property 'Schema' is used before its initialization.
})
constructor(id: string, data: ClassB[]) {
this.id = id;
this.data = data;
}
public id: string;
public data: ClassB[];
}
class ClassB {
static Schema = Type.Object({
id: Type.String(),
})
constructor(id: string) {
this.id = id;
}
public id: string;
}
Problem is I can't pass ClassB.Schema as argument for Type.Array(), I got the error: Property 'Schema' is used before its initialization. I thought that since both are static properties, they are evaluated at the same time but it doesn't seem the case. Any idea how to workaround this?
Well, it seems it is enough to invert the order of class declarations...
import {Type, Static} from '#sinclair/typebox';
class ClassB {
static Schema = Type.Object({
id: Type.String(),
})
constructor(id: string) {
this.id = id;
}
public id: string;
}
class ClassA {
static Schema = Type.Object({
id: Type.String(),
data: Type.Array(ClassB.Schema)
})
constructor(id: string, data: ClassB[]) {
this.id = id;
this.data = data;
}
public id: string;
public data: ClassB[];
}
Related
I Have simple autoloader method that initializes and returns Instance of class using require()
Above simple require is some logic requesting info from database and checking if class exists in filesystem.
let elementClass = require('./elements/' + element.name).default;
Node and ts-node compiles without problem, but when creating instance by require i get
ReferenceError: Cannot access 'Object' before initialization
Object is main abstract class being extended by Element which is then extended by element type.
Required is last in chain class elementType
Class is initialized without problem if there is no Object class being extended, but Object class was tested without any modifications and blank constructor. And all logic transfered to Element class
Object.ts
export default class Object
{
public id: number;
public name: string;
public type: string;
public parent: number;
public owner: number;
public permission: string;
public data: {[name: string]: any};
protected dataSchema: {[name: string]: any} = {};
constructor(id: number, name: string, type: string, parent: number, owner: number, permission: string, data: {[name: string]: any})
{
if(new Validator().validate(data, this.dataSchema))
throw new Error('Invalid input data');
this.id = id;
this.name = name;
this.type = type;
this.parent = parent;
this.owner = owner;
this.permission = permission;
this.data = data;
}
}
Element.ts
import Object from "./object";
export default class Element extends Object
{
/**
* simple logic no constructor
*/
}
ElementType.ts
import Element from '../element';
export default class ElementType extends Element
{
/**
* Simple logic without constructor
*/
}
Even without any simple methods inside classes it doesn't work, it only works when Main Parent class is not used (Object)
Is there some problem requiring class that is twice extended?
Object is reserved word, I just had to rename class to something else.
This may be looking similar to previous questions, but I didn't find my answer, so I asked it here.
My example:
Class A{
id:number;
name:string;
}
Class B{
id:number;
name:string;
age:number;
grade:number;
}
const b = new B {id:1, name:'John', age: 20, grade: 10}
Now I want to initialize a new instance of class A only with two attributes from class B.
Something like this:
const a = b as A;
I need my result look like this:
a = {id:1, name:'John'}
Thanks,
Code
class A {
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
static toA<T extends { id: number, name: string }>({ id, name }: T): A {
const newObject = new A(id, name);
return newObject;
}
}
Explanation
It is not possible to change the runtime structure by only cheating TypeScript. as just tells TypeScript "Don't think too much, just assume b as A," and TypeScript won't do anything (including writing a B to A converter for you) except assuming the type of that object is A in the type-checking (compile) time.
So, We should write an explicit converter for it. We received any object with the required fields, then create a new A object with those fields.
Examples
class A {
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
static toA<T extends { id: number, name: string }>({ id, name }: T): A {
const newObject = new A(id, name);
return newObject;
}
}
class B extends A {
test: number;
constructor(id: number, name: string, test: number) {
super(id, name);
this.test = test;
}
}
const b = new B(1, "hi", 2);
console.log(A.toA(b)); // A { id: 1, name: 'hi' }
Let's assume there is a model class like:
export abstract class Target {
id!: number;
name!: string;
}
export class Target1 extends Target {
name = 'target1';
id: number;
kind: string;
constructor(id: number, kind: string) {
super();
this.id = id;
this.kind = kind;
}
static getForm(){
return new FormGroup({...});
}
}
export class Target2 extends Target {
name = 'target2';
id: number;
address: string;
constructor(id: number, address: string) {
super();
this.id = id;
this.address = address;
}
static getForm(){
return new FormGroup({...});
}
}
....
export class TargetN extends Target {}
type Measure = {
type: string;
target: any; // Here is what I need to correct the type
}
const measures: Measure[] = [
{ type: 'measure1', target: Target1},
{ type: 'measure2', target: Target2},
...
{ type: 'measureN', target: TargetN},
];
In the form, I allow user input address or kind based on situation user selected measures.type then I will instance a new target as bellow:
const inputValue = 'http://localhost:3000';
const selectedType = measures[0].type;
const measure = measures.find(m => m.type === selectedType)!;
const target = new measure.target(1, inputValue);
const form = measure.target.getForm();
console.log(target.kind); //
...
Everything is working fine. But which annoys me is that I don't know how to put the correct type at Measure -> target instead of any:
type Measure = {
type: string;
target: any; // ???
}
If I give it a type Target like bellow:
type Measure = {
type: string;
target: Target;
}
then I will get the error
Did you mean to use 'new' with this expression?
And, if I give it typeof like bellow:
type Measure = {
type: string;
target: typeof Target;
}
then I will get the error
Type 'typeof Target1' is not assignable to type 'typeof Target'.
How can I replace type any in target with another specificity type?
If I use ThisType it looks good
type Measure = {
type: string;
target: ThisType<Target>;
}
but the static method inside Target1 or Target2 it will throw error
Property 'getForm' does not exist on type 'ThisType<Target>'.
I also tried with Required like below:
type RequiredTarget = Required<typeof Target>;
type Measure = {
type: string;
target: RequiredTarget;
}
but it's not working too.
I appreciate your help with that.
I would approach this with the following manner:
type S = string;
type I = number;
interface IY {
type?: string;
target?: S | I;
}
type Y = IY
const U: Y = { type: "U", target: "1" }
const V: Y = { type: "V", target: 2 }
const X: Y = {}
How do you get a Typescript class decorator to call a method that sets any property or method starting with an "_" as non-enumerable property (essentially making it private for serialization purposes)?
Imagine there's a class that's a base class which every other class in my application will extend from:
class Base {
constructor(data: any){
Object.assign(this, data)
}
_setNonEnumerableProperties(){
Object.keys(this).forEach(key => {
if(key[0] === '_')
Object.defineProperty(this, key, { enumerable: false })
})
}
}
And then I have a User class like this:
#nonEnumerablePrivateMembers
class User extends Base {
public name: string
public email: string
private _active: boolean
}
And I create and JSON stringify a User instance like this:
const user = new User({name: 'John', email: 'john#example.com', _active: false})
const jsonUser = JSON.stringify(user)
console.log(jsonUser)
I expect the output to be this:
{ "name": "John", "email": "john#example.com" }
and not this:
{ "name": "John", "email": "john#example.com", "_active": false }
Note that it doesn't include the _active property.
I need to know how to write nonEnumerablePrivateMembers decorator that would call the _setNonEnumerableProperties method on the Base class upon a new instance of the extended class.
Any suggestions?
Overriding the constructor appears to work as intended:
interface Class<T> {
new(...args: any[]): T
}
function nonEnumerablePrivateMembers<T extends Class<Base>>(cls: T): T {
return class extends cls {
constructor(...args: any[]) {
super(...args);
this._setNonEnumerableProperties();
}
}
}
Playground
I'm sure someone with better TypeScript skills can modify this to be a bit cleaner, but based on the TypeScript decorator docs you can have something like this:
function fixEnumerables<T extends { new (...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
constructor(...data: any[]) {
super(data);
Object.assign(this, data[0]);
Object.keys(this).forEach(key => {
if (key[0] === "_") {
console.log(`Freezing "${key}"`);
Object.defineProperty(this, key, { enumerable: false });
}
});
}
};
}
#fixEnumerables
class User {
public name: string;
public email: string;
private _active: boolean;
constructor(data: any) {}
}
const x = new User({ name: "Sam", email: "email#example.com", _active: false });
console.log(JSON.stringify(x));
// Output
// Freezing "_active"
// {"name":"Sam", "email": "email#example.com"}
Blitz
I'm new in TypeScript.
I'm getting error when trying to instantiating the class.
Below is my sample code, actual code is different can't share.
module ABC {
export class A {
public execute<T>(action: string, data?: any, callerContext?: any): IAsyncResult<T> {
// CODE::
var requestMessage = new Common.ClientClasses.ClientRequestMessage(); **// **ERROR- "WinRTError: Class not registered"****
requestMessage.requestUri = actionRequest;
requestMessage.method = "POST";
requestMessage.body = data ? JSON.stringify(data, null, 2) : null;
Common.ClientClasses.ClientRequest.executeAsync(requestMessage)
.done((result: Common.ClientClasses.ClientResponeMessage) => {
// CODE:
}
// Code::
}
}
}
declare module Common.ClientClasses {
class ClientRequestMessage {
public requestUri: string;
public method: string;
public body: string;
}
class ClientResponeMessage {
public status: number;
public statusText: string;
public responseText: string;
}
class ClientRequest {
static executeAsync(clientRequestMessage: ClientRequestMessage): any;
}
}
I did some improvements, should work:
module ABC {
export class A {
public execute<T>(action: string, data?: any, callerContext?: any) {
var requestMessage = new Common.ClientClasses.ClientRequestMessage();
requestMessage.method = "POST";
requestMessage.body = data ? JSON.stringify(data, null, 2) : null;
Common.ClientClasses.ClientRequest.executeAsync(requestMessage)
}
}
}
module Common.ClientClasses {
export class ClientRequestMessage {
public requestUri: string;
public method: string;
public body: string;
}
class ClientResponeMessage {
public status: number;
public statusText: string;
public responseText: string;
}
export class ClientRequest {
static executeAsync(clientRequestMessage: ClientRequestMessage): any {
console.log("test");
}
}
}
Then it can be run as following:
var a = new ABC.A();
a.execute("some string");
declare module creates a definition file used for Intellisense but it doesn't provide any implementation that's why I changed your code so this fragment can work.
Also if you want to use any classes from the module, you must export them so they can be visible from outside of that module.