I am trying to export some variation of a class that I want to import somewhere else.
I don't know it is possible to create them without instantiation?
And how I can do that.
This is what I have now
index.ts
export { Character } from './Character';
Character.ts
import { CharacterOptions, WarlockOptions } from './CharacterOptions';
class Character implements CharacterInterface {
private health: number;
private name: string;
private characterOptions: CharacterOptions;
constructor(name, health) {
this.name = name;
this.health = health;
this.characterOptions = new WarlockOptions(); // where WarlockOptions extends CharacterOptions
}
}
I would like to be able to do something like that in the index.ts file
import { Character } from './Character';
import { ArcherOptions, WarlockOptions } from './CharacterOptions';
export const ArcherClass = someWrapperOfCharacter(ArcherOptions);
export const WarlockClass = someWrapperOfCharacter(WarlockOptions);
like dynamically creating (through someWrapperOfCharacter()) a new specific class that I can expose.
I know that I could create directly classes that extend Character but I try to avoid that because:
I don't know how many CharacterOptions I will have in the future
I would like not to be forced to create a new variation each time I want to add a new CharacterOptions
I want to allow to create directly a custom class by passing a custom object that extends CharacterOptions
You could pass in the constructor for options to the class, and have a function that creates derived types that set the options class to a specific implementation:
interface CharacterInterface { }
class CharacterOptions { public level?: number }
class ArcherOptions extends CharacterOptions { public bow?: string; }
class WarlockOptions extends CharacterOptions { public magic?: string }
class Character<T extends CharacterOptions> implements CharacterInterface {
private health: number;
private name: string;
private characterOptions: T;
constructor(name: string, health: number, optionsCtor: new () => T) {
this.name = name;
this.health = health;
this.characterOptions = new optionsCtor(); // where WarlockOptions extends CharacterOptions
}
}
function someWrapperOfCharacter<T extends CharacterOptions>(optionsCtor: new () => T) {
return class extends Character<T> {
constructor(name: string, health: number) {
super(name, health, optionsCtor);
}
}
}
export const ArcherClass = someWrapperOfCharacter(ArcherOptions);
export type ArcherClass = InstanceType<typeof ArcherClass> // needed to allow type declarations let a: ArcherClass
export const WarlockClass = someWrapperOfCharacter(WarlockOptions);
export type WarlockClass = InstanceType<typeof WarlockClass>
I tried Titian answer but it didn't satisfied me completely (although it helped me a lot).
I finally wrote the wrapper like this:
interface CharacterInterface { }
class CharacterOptions { public level?: number }
class ArcherOptions extends CharacterOptions { public bow?: string; }
class WarlockOptions extends CharacterOptions { public magic?: string }
export const someWrapperOfCharacter = (optionsCtor: CharacterOptions) => {
return class Character implements CharacterInterface {
private health: number;
private name: string;
private characterOptions: CharacterOptions;
constructor(name: string, health: number) {
this.name = name;
this.health = health;
this.characterOptions = new optionsCtor();
}
}
};
export const ArcherClass = someWrapperOfCharacter(ArcherOptions);
export type ArcherClass = InstanceType<typeof ArcherClass> // needed to allow type declarations let a: ArcherClass
export const WarlockClass = someWrapperOfCharacter(WarlockOptions);
export type WarlockClass = InstanceType<typeof WarlockClass>
const archer = new ArcherClass('archie', 100);
console.log(archer instanceof ArcherClass); // true
const warlock = new WarlockClass('warlie', 100);
console.log(warlock instanceof WarlockClass); // true
Related
For example, using mix-ins to extends multiple utility classes, like this:
import { autorun, makeObservable } from "mobx";
type GConstructor<T = {}> = new (...args: any[]) => T;
interface HasLife {
grow: () => void;
}
class Animal {
age: number;
constructor(age: number) {
this.age = age;
}
}
function addLife<TBase extends GConstructor<Animal>>(Base: TBase) {
return class Jumpable extends Base {
// this is wrong, mixins class cannot use constructor!
constructor() {
super();
makeObservable(this, {
age: observable,
grow: action,
});
}
grow() {
this.age++;
}
};
}
class Dog extends addLife(Animal) implements HasLife {
constructor(age: number) {
super(age);
}
}
const dog = new Dog(0);
autorun(() => {
console.log(dog.age);
});
dog.grow();
The example I provided cannot run correctly because mixin classes cannot have constructors, but I would also like to mark the member methods as observable or action in the mixin classes.
What should I do?
or, What is the common approach within the mobx community for dealing with this problem using other methods?
I'm developing an Entity-Component-System in TypeScript where entities contain a map of their components. CT stands for ComponentType Here is the code:
class Entity {
components: Map<CT, Component>;
constructor() {
this.components = new Map();
}
get(componentType: CT): Component {
return this.components.get(componentType);
}
}
const enum CT {
Position,
Health // etc..
}
class Component {
type: CT;
constructor(type: CT) {
this.type = type;
}
}
class HealthComponent extends Component {
amount: number;
constructor() {
super(CT.Health);
this.amount = 0;
}
}
The problem is that when I do something like:
let healthComponent = new HealthComponent();
healthComponent.amount = 100;
let entity = new Entity();
entity.components.set(healthComponent.type, healthComponent);
let position = entity.get(CT.Health);
console.log(health.amount);
I get this error: Property 'amount' does not exist on type 'Component'
If I change the Entity.get function to instead return any like this, then the error goes away:
get(componentType: CT): any {
return this.components.get(componentType);
}
But then I no longer get code-completion, which I would like.
Is it possible to have it return the proper type, so I can have the code-completion and error-detection?
class Component {
// error: 'amount' does not exist on type 'Component'
amount: number; // <-- NOTE: moved amount here from HealthComponent
type: CT;
constructor(type: CT) {
this.type = type;
}
}
class HealthComponent extends Component {
// removed amount from here
constructor() {
super(CT.Health);
this.amount = 0;
}
}
I managed to hack something together using TypeScript's conditional types. It's not ideal but it might work for you:
Let's declare the enum and component classes first:
const enum CT {
Position,
Health
}
class HealthComponent {
amount: number;
constructor() {
this.amount = 0;
}
}
class PositionComponent {
position: number;
constructor() {
this.position = 1;
}
}
Then the conditional type that maps the enum values to component classes:
type TypeMapper<T extends CT> = T extends CT.Position
? PositionComponent
: HealthComponent;
It's a generic type that based on it's type parameter T either evaluates to the PositionComponent or the HealthComponent class.
And now the entity class:
class Entity {
private components: Map<CT, TypeMapper<CT>>;
constructor() {
this.components = new Map();
}
set<T extends CT>(ct: T, component: TypeMapper<T>) {
this.components.set(ct, component);
}
get<T extends CT>(ct: T): TypeMapper<T> {
return this.components.get(ct) as any; //a little lie here to shut up the TS compiler
}
}
Now when you instantiate everything, the types should align for you correctly:
const entity = new Entity();
entity.set(CT.Health, new HealthComponent());
const health = entity.get(CT.Health);
health.amount //and the amount prop should show up in intellisense correctly.
As I said, it's not ideal because it relies on using any, also depending on the number of component classes you have, your TypeMapper type can become huge, but at least the Component base class and super() calls to it's constructor are no longer necessary so you win some lines of code there.
Here's the TS playground link.
I need help using class mixins in declaration files. Specifically, when a method is defined in a mixin, typescript is not picking it up in the mixed class body:
In my case, I am applying two mixins. The first mixin - NotifyingElementMixin - provides a method called notify, and it's this method which is failing to apply to the mixed class body
notifying-element-mixin.js
export const NotifyingElementMixin = superclass =>
class NotifyingElement extends superclass {
/**
* Fires a `*-changed` event.
*
* #param {string} propName Name of the property.
* #param {any} value property value
* #protected
*/
notify(propName, value) {
this.dispatchEvent(
new CustomEvent(`${propName}-changed`, {
detail: { value },
})
);
}
};
};
notifying-element-mixin.d.ts
export declare class NotifyingElement {
public notify(propName: string, value: any): void
}
export function NotifyingElementMixin<TBase extends typeof HTMLElement>
(superclass: TBase): TBase & NotifyingElement;
The second mixin provides other properties and methods, but for the sake of this question, I've simplified the implementation
apollo-query-mixin.js
export const ApolloQueryMixin =
superclass => class extends superclass {
data = null;
is = 'Query';
};
apollo-query-mixin.d.ts
export declare class ApolloQuery<TCacheShape, TData, TVariables, TSubscriptionData = TData> {
data: null
is: string
}
type Constructor<T = HTMLElement> = new (...args: any[]) => T;
export function ApolloQueryMixin<TBase extends Constructor, TCacheShape, TData, TVariables>
(superclass: TBase): ApolloQuery<TCacheShape, TData, TVariables> & TBase;
Finally, I want to export a class which applies both mixins and provides it's own methods as well. This is where I run into trouble
apollo-query.js
class ApolloQuery extends NotifyingElementMixin(ApolloQueryMixin(HTMLElement)) {
/**
* Latest data.
*/
get data() {
return this.__data;
}
set data(value) {
this.__data = value;
this.notify('data', value);
}
// etc
}
apollo-query.d.ts
import { ApolloQueryMixin } from "./apollo-query-mixin";
import { NotifyingElementMixin } from "./notifying-element-mixin";
export declare class ApolloQuery<TBase, TCacheShape, TData, TVariables>
extends NotifyingElementMixin(ApolloQueryMixin(HTMLElement)) {}
When I compile this, or use my IDE, I receive the error:
error TS2339: Property 'notify' does not exist on type 'ApolloQuery'.
How do I finagle typescript into picking up my inherited methods in the mixed class body?
Here's the mixin pattern I use, I think the key is the return constructor:
import { LitElement, property } from "lit-element";
type Constructor = new (...args: any[]) => LitElement;
interface BeforeRenderMixin {
beforeRenderComplete: Boolean;
}
type ReturnConstructor = new (...args: any[]) => LitElement & BeforeRenderMixin;
export default function<B extends Constructor>(Base: B): B & ReturnConstructor {
class Mixin extends Base implements BeforeRenderMixin {
#property({ type: Boolean })
public beforeRenderComplete: boolean = false;
public connectedCallback() {
super.connectedCallback();
if (!this.beforeRenderComplete)
this.beforeRender().then(() => (this.beforeRenderComplete = true));
}
public async beforeRender() {
return;
}
public shouldUpdate(changedProperties: any) {
return this.beforeRenderComplete && super.shouldUpdate(changedProperties);
}
}
return Mixin;
}
which generates:
import { LitElement } from "lit-element";
declare type Constructor = new (...args: any[]) => LitElement;
interface BeforeRenderMixin {
beforeRenderComplete: Boolean;
}
declare type ReturnConstructor = new (...args: any[]) => LitElement & BeforeRenderMixin;
export default function <B extends Constructor>(Base: B): B & ReturnConstructor;
export {};
I am trying to port (in a reduced manner) the following sample to typescript, as I want to DEMO the abstract factory pattern in typescript.
https://gerardnico.com/wiki/lang/java/dao
So far the code I have created is the following (structure only)
export default class Customer{
public id: string;
public firstName: string;
public lastName: string;
}
import Customer from "./Customer";
export default interface ICustomerDAO{
insertCustomer(): number;
deleteCustomer(): boolean;
findCustomer(): Customer;
updateCustomer(): boolean;
listCustomers(): Customer[];
}
import CustomerDAO from "./ICustomerDAO";
import SharepointListDAOFactory from "./SharepointListDAOFactory";
import JsonDAOFactory from "./JsonDAOFactory";
export default abstract class DAOFactory{
public static SHAREPOINTLIST: number = 1;
public static REMOTEJSON : number = 2;
public abstract getCustomerDAO(): CustomerDAO;
public getDAOFactory(whichFactory: number): DAOFactory {
switch (whichFactory) {
case 1:
return new SharepointListDAOFactory();
case 2:
return new JsonDAOFactory();
default :
return null;
}
}
}
import DAOFactory from "./DAOFactory";
import ICustomerDAO from "./ICustomerDAO";
import JsonCustomerDAO from "./JsonCustomerDAO";
export default class JsonDAOFactory extends DAOFactory{
getCustomerDAO(): ICustomerDAO{
return new JsonCustomerDAO();
}
}
import DAOFactory from "./DAOFactory";
import ICustomerDAO from "./ICustomerDAO";
import SharepointCustomerDao from "./SharepointCustomerDAO";
export default class SharepointListDAOFactory extends DAOFactory{
getCustomerDAO(): ICustomerDAO{
return new SharepointCustomerDao();
}
}
import ICustomerDAO from "./ICustomerDAO";
import Customer from "./Customer";
//actual implementation
export default class JsonCustomerDAO implements ICustomerDAO{
public insertCustomer(): number{
return 1;
}
public deleteCustomer(): boolean{
return true;
}
public findCustomer(): Customer{
return new Customer();
}
public updateCustomer(): boolean{
return true;
}
public listCustomers(): Customer[]{
let c1= new Customer();
let c2= new Customer();
let list: Array<Customer> = [c1, c2 ];
return list;
}
}
import ICustomerDAO from "./ICustomerDAO";
import Customer from "./Customer";
//actual implementations
export default class SharepointCustomerDao implements ICustomerDAO{
public insertCustomer(): number{
return 1;
}
public deleteCustomer(): boolean{
return true;
}
public findCustomer(): Customer{
return new Customer();
}
public updateCustomer(): boolean{
return true;
}
public listCustomers(): Customer[]{
let c1= new Customer();
let c2= new Customer();
let list: Array<Customer> = [c1, c2 ];
return list;
}
}
Code compiles, but I am having issues in how to use the abstract class, from the client or caller of the above code, which is an SPFx REACT component
import * as React from 'react';
import styles from './TypescriptDesignPatterns02AbstractFactory.module.scss';
import { ITypescriptDesignPatterns02AbstractFactoryProps } from './ITypescriptDesignPatterns02AbstractFactoryProps';
import { escape } from '#microsoft/sp-lodash-subset';
import { ITypescriptDesignPatterns02AbstractFactoryState } from './ITypescriptDesignPatterns02AbstractFactoryState';
import SharepointListDAOFactory from './Factory/SharepointListDAOFactory';
import DAOFactory from './Factory/DAOFactory';
export default class TypescriptDesignPatterns02AbstractFactory extends React.Component<ITypescriptDesignPatterns02AbstractFactoryProps, ITypescriptDesignPatterns02AbstractFactoryState> {
constructor(props: ITypescriptDesignPatterns02AbstractFactoryProps, state: ITypescriptDesignPatterns02AbstractFactoryState) {
super(props);
this.setInitialState();
}
public render(): React.ReactElement<ITypescriptDesignPatterns02AbstractFactoryProps> {
switch(this.props.datasource) {
case "Sharepoint":
let sharepointlistdaofactory: SharepointListDAOFactory = DAOFactory.getDAOFactory(1);
break;
case "JSON":
break;
}
return null;
}
public setInitialState(): void {
this.state = {
items: []
};
}
}
I am not sure about the syntax or how to translate these lines to typescript
DAOFactory OracleDbFactory =
DAOFactory.getDAOFactory(DAOFactory.DAOORACLEDB);
// Create a DAO
CustomerDAO custDAO =
OracleDbFactory.getCustomerDAO();
Currently your code would require instantiation of a DAOFactory to have acess to the getDAOFactory method:
const daoFactory = new DAOFactory;
However you can't do this since DAOFactory is abstract.
Instead the factory function doesn't need to be an object method -- it can be a class method (static) or even just a function.
const getDAOFactory = (whichFactory: number): DAOFactory => {
switch (whichFactory) {
case 1:
return new SharepointListDAOFactory();
case 2:
return new JsonDAOFactory();
default :
return null;
}
}
Now you can just call OracleDbFactory = getDAOFactory(DAOFactory.DAOORACLEDB); (type is implicit from the return type of the function).
I do wonder why you need this extra layer of abstraction though. Seems like something like custDao = getDao(DaoTypes.Customer) would suffice.
I have an app that initializes by running its method .init(params) like this:
app.init([TopBar, StatusBar, MainArea]);
Where TopBar, StatusBar and MainArea are classes, not instances of classes. Each of these classes implements the same interface IComponent.
I want to instantiate objects from the passed classes in the .init(params) method, like this:
init(params: IComponent[]): void {
params.map(function (component) {
let comp = new component();
this.components[comp.constructor.name] = comp;
}, this);
The issue is that as these are not instance, TypeScript doesn't know their types and throws an error:
error TS2345: Argument of type '(typeof TopBar | typeof StatusBar |
typeof MainArea)[]' is not assignable to parameter of type
'IComponent[]'.
How do I fix the code so that I could pass an array of classes that implement some interface to a method?
Typescript supports Class Type Generics (TypeScript Docs). Their example is:
function create<T>(c: {new(): T; }): T {
return new c();
}
Which says "Pass into my create method a class that when constructed will return the type T that I want". This signature will prevent you from trying to pass in any class type that isn't of type T.
This is close to what we want, we just need to adjust for it being an array of items and items of your IComponent.
public init(components: {new(): IComponent;}[]): void {
// at this point our `components` variable is a collection of
// classes that implement IComponent
// for example, we can just new up the first one;
var firstComponent = new components[0]();
}, this);
With the method signature, we can now use it like
app.init([TopBar, StatusBar, MainArea]);
Where we pass in the array of types that implement IComponent
There is a working typescript playground (run it to get alert with result)
what we need is to create a custom type InterfaceComponent. That will be expected as an array of the init() method
interface IComponent { }
class TopBar implements IComponent { }
class StatusBar implements IComponent { }
class MainArea implements IComponent { }
// this is a type we want to be passed into INIT as an array
type InterfaceComponent = (typeof TopBar | typeof StatusBar | typeof MainArea);
class MyClass {
components: {[key:string] : IComponent } = {};
init(params: (InterfaceComponent)[]): void {
params.map((component) => {
let comp = new component();
this.components[comp.constructor["name"]] = comp;
}, this);
}
}
let x = new MyClass();
x.init([TopBar, StatusBar, MainArea])
alert(JSON.stringify(x.components))
Check it here
Even though this is an old question: this is how you do it:
interface IComponent { something(): void; }
class TopBar implements IComponent { something() { console.log('in TopBar'); }}
class StatusBar implements IComponent { something() { console.log('in StatusBar'); }}
class MainArea implements IComponent { something() { console.log('in MainArea'); }}
interface ComponentClass {
new(): IComponent;
}
const components: { [name: string]: IComponent } = {};
function init(params: ComponentClass[]) {
params.map((component) => {
let comp = new component();
components[component.name] = comp;
});
}
init([TopBar, StatusBar, MainArea]);
for (const c in components) {
console.log('Component: ' + c);
components[c].something();
}
Use a factory method instead. The declaration is a bit clumsy but the idea works:
interface InterfaceComponent {
name: string;
}
class TopBar implements InterfaceComponent {
name: string;
}
class StatusBar implements InterfaceComponent {
name: string;
}
class MainArea implements InterfaceComponent {
name: string;
}
interface InterfaceComponentFactory {
create: () => InterfaceComponent;
}
function init(params: InterfaceComponentFactory[]): void {
params.map(function (component) {
let comp = component.create();
this.components[comp.name] = comp;
}, this);
}
init([{ create: () => new TopBar() }, { create: () => new StatusBar() }, { create: () => new MainArea() }]);
Perhaps you could specify the type of comp as InterfaceComponent.
var comp: InterfaceComponent = new component();
this.components[comp.constructor.name] = comp;
I found two different ways you can create types for this situation:
// Interface style:
export default interface IConstructor<T> extends Function {
new (...args: any[]): T;
}
// Union Type style:
export type ConstructorUnion<T> = new(...args : any[]) => T;
So this is how it would look with the IConstructor type:
interface IComponent { }
class TopBar implements IComponent { }
class StatusBar implements IComponent { }
class MainArea { }
class App {
public components: { [key: string]: IComponent } = {};
public init(params: IConstructor<IComponent>[]): void {
params.forEach((Component: IConstructor<IComponent>) => {
const comp: IComponent = new Component();
this.components[comp.constructor.name] = comp;
});
}
}
const app = new App();
app.init([TopBar, StatusBar, MainArea]);
console.clear();
console.log(app);
Here is the code:
https://stackblitz.com/edit/how-to-type-an-array-with-classes-in-typescript?file=index.ts