Type: class extends by abstract class - javascript

I have abstract class:
abstract class DataType<D> {
constructor(protected data: D) {}
...other
}
and class extended by my abstract class:
type PersonData = {
firstName: string;
lastName: string;
...other props
}
class Person extends DataType<PersonData> {
...methods with body specific for person
}
I need save class Person (and other) in const like that:
const DataClasses: DataConstructor = {
person: Person
...other
}
i need use it like that:
new DataClasses['person']({...PersonData}) // exacly is same as: new Person({...PersonData})
but I've problem with DataConstructor type. I don't know how do this dynamically. I tried many combinations and the closest is:
type DataConstructor = {
[name: string]: new (data: ConstructorParameters<typeof Person>[0]) =>
DataType<ConstructorParameters<typeof Person>[0]>
}
but I must add Person class in typeof. I need this dynamically. DataConstructor must accept any class extends by DataType and returns object to use that: new DataClasses['person'](PersonDataObjec). All clasess has only one property in constructor - data.

Related

How to list properties of a Nestjs DTO class?

I have following Nestjs DTO class:
// create-job-offer.dto.ts
import { IsOptional, IsNumber } from 'class-validator';
export class CreateJobOfferDto {
#IsNumber()
#IsOptional()
mentorId: number;
#IsNumber()
#IsOptional()
companyId: number;
}
I want to obtain the list of class properties: ['mentorId', 'companyId'].
I tried so far in a controller without success following methods:
Object.getOwnPropertyNames(new CreateJobOfferDto());
Object.getOwnPropertyNames(CreateJobOfferDto);
Object.getOwnPropertySymbols(new CreateJobOfferDto());
Object.getOwnPropertySymbols(CreateJobOfferDto);
Object.getOwnPropertyDescriptors(CreateJobOfferDto);
Object.getOwnPropertyDescriptors(new CreateJobOfferDto());
Object.getPrototypeOf(CreateJobOfferDto);
Object.getPrototypeOf(new CreateJobOfferDto());
If I add a method, or vars in a constructor, I can get them, but not the properties.
The reason why I want to achieve this is, I am using Prisma and React, and in my React app I want to receive the list of class properties so that I can generate a model form dynamically.
There is no easy direct way to get the list of properties of a DTO class, or
more comprehensively, any type of class that has only properties.
The reason is that whenever you defined a property without any values, it becomes disappears after compiling to javascript.
Example:
// typescript
class A {
private readonly property1: string;
public readonly property2: boolean;
}
it compiles to this:
// javascript
"use strict";
class A {}
In order to achieve this goal, you need to write a custom decorator. like this:
const properties = Symbol('properties');
// This decorator will be called for each property, and it stores the property name in an object.
export const Property = () => {
return (obj: any, propertyName: string) => {
(obj[properties] || (obj[properties] = [])).push(propertyName);
};
};
// This is a function to retrieve the list of properties for a class
export function getProperties(obj: any): [] {
return obj.prototype[properties];
}
Imagine I have a DTO class for a user, like this:
import { getProperties } from './decorators/property.decorator';
export class UserDto {
#Property()
#IsNotEmpty()
firstName: string;
#Property()
#IsEmail()
#IsOptional()
public readonly email: string;
}
and I want to get all properties in a list, so we need to call the getProperties method which we defined earlier, so:
import { UserDto } from './dtos/user.dto';
getProperties(UserDto); // [ 'firstName', 'email' ]
You can also use an npm module ts-transformer-keys.
A TypeScript custom transformer which enables to obtain keys of given type.
How to use:
import { keys } from 'ts-transformer-keys';
interface Props {
id: string;
name: string;
age: number;
}
const keysOfProps = keys<Props>();
console.log(keysOfProps); // ['id', 'name', 'age']

How to describe an index type with generic values in Typescript?

Is there any way to describe this interface in typescript:
abstract class Entity { /*...*/ }
interface List<A extends Entity> { /*...*/ }
// I want the below interface
interface Collections {
[name: string]: List<A extends Entity>;
}
The Collections object contains different List objects of classes that extend the Entity class. So the following code doesn't solve my problem:
interface Collections<A extends Entity> {
[name: string]: List<A>;
}
The above interface serves only for one single class A, I want it to serve multiple classes extend from Entity class.
Thank you!
If I caught the idea, this might be the solution?
abstract class Entity { /*...*/ }
class Book extends Entity { title = 'default'; }
class Spoon extends Entity { price = 5; }
interface List<A> { /*...*/ }
interface EntityList extends List<Entity>{}
interface Collections {
[name: string]: EntityList
}
const t: Collections = {
'test': [new Book(), new Spoon()]
}
const x = t['test'];
If you don't wont to close type you might do this:
interface Collections<T> {
[name: string]: List<T>
}
const t: Collections<Entity> = {
'test': [new Book(), new Spoon()]
}

Extending from a class that extended from a Generic Class in typescript

I've got a chain of classes that all use the same constructor in typescript. I'd like to ensure it receives the same class as the object itself.
class Node<T> {
readonly id: number
constructor (data: T) {
Object.assign(this, data)
}
}
class User extends Node<User> {
readonly name: string
}
class CoolUser extends User {
readonly coolness: number
}
const node = new Node({ id: 3 })
const user = new User({ id: 4, name: 'bob' })
const coolUser = new CoolUser({ id: 4, name: 'super cool person', coolness: 7 })
The last line fails type checking since coolness isn't a property of user. I'm set on using the generic class approach but I'm unsure how to define the constructor input types to check properly.
You have to make User generic, too.
class Node<T> {
readonly id: number;
constructor(data: T) {
Object.assign(this, data);
}
}
class User<T> extends Node<T> {
readonly name: string;
}
class CoolUser extends User<CoolUser> {
readonly coolness: number;
}

Typescript pass generic to Map

I want to pass a generic type from the child class to a property.
interface MyInterface {
method(): void;
}
class B<P> {
entities = new Map<number, P>();
}
class C<P = MyInterface> extends B<P> {
click() {
this.entities.get(1).method();
}
}
I'm expecting each entity to be of type MyInterface, but I'm getting a type error:
Property method() doesn't exist on type P
What is the issue?
Just because P = MyInterface does not mean any passed in P must extend MyInterface. For example with no other constraints, this would also be valid:
new C<{ noMethod() : void }>();
You need to add a constraint to P to say that any passed in P must extend MyInterface, and you can keep MyInterface as a default as well.
interface MyInterface {
method(): void;
}
class B<P> {
entities = new Map<number, P>();
}
class C<P extends MyInterface = MyInterface> extends B<P> {
click() {
this.entities.get(1).method();
}
}

Typescript Create Generic Object

I'm trying to create an object in typescript.
I want to do this generically so that it automatically gets it's default based off of its type
interface IModal {
content: string;
count: number
}
Normally I would declare an Instance of it like so:
var modal: IModal = {
content: '',
count: 0
};
I don't want to have to do this everytime. I want to do this so that the it automatically create an instance of the interface with Typed default i.e
number = 0,
string = '',
boolean = false,
MyCustom = new MyCustom
I want to do something like this:
export class MakeObject {
Make<T> : T = function(iInterface) => {
return default(T);
}
}
But I don't think that will work
Interfaces can extends classes, so you can make it like this:
class DefaultModel {
content = ''
count = 0
}
export interface IModel extends DefaultModel {}
/* since interface generates no code,
* you can also export the default instance using the same name, if you like
*/
export const IModel: IModel = new DefaultModel
// or export the default class, if you're more interested in it
export const IModel = DefaultModel
another way to do it, if you are not against classes, is using abstract classes, in typescript they are as flexible as interfaces (eg: interchangeable with type) and exist at runtime:
abstract class BaseModel {
content = ''
count = 0
}
const modal = new (class extends BaseModel {})
Note that with this approach content and count are not abstract, so while you can still use BaseModel for typecheck, if you need to force subclasses to provide their own values, you may still create an interface to clear the default:
class SomeModel extends BaseModel {} // OK
interface IModel extends BaseModel {}
class SomeModel implements IModel {} // NOT OK: Property 'content' is missing
I want to do this so that the it automatically create an instance of the interface with Typed default i.e
You can't do this magically as interfaces do not exist at runtime. That said you can write code to do this quite easily:
interface Model {
content: string;
count: number
}
function createModel({
// Add defaults
content = '',
count = 0
} = {}): Model {
return { content, count }
}
createModel(); // Okay
createModel({ count: 1 }); // Okay
createModel({ counted: 1 }); // Error: Typo

Categories

Resources