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?
Related
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 using typescript and i am writing a custom decorator for one of my angular class. I want to access the base class method in the child class decorator. Or access base class methods using the child class prototype. Is there any way to do this? Problem explained in detail below.
Scenario
I have a base class which is like
export class Base {
public init() {
console.log('My base class function');
}
}
And i have a derived class which extends this base class
export class Child extends Base {
}
What i am trying to do
I am trying to write a decorator for the derived class something like
#TestDecorator(['init'])
export class Child extends Base {
}
which will call the init method from the base class.
What is the issue
To do the above scenario, i have written code something like below
export function Tool<T extends Base>(methods: any[]) {
return function (target: Function) {
methods.forEach((item) => {
if (item === 'init') {
target.super.init() // Stuck here
}
})
}
}
I am not understanding how to make the following line work
target.super.init() // Stuck here
Please help me with the solution. I am stuck. Thanks
I believe you are looking for something like this:
export function Tool<T extends Base>(methods: any[]) {
return function (target: Function) {
return class extends target {
constructor(...args: any[]) {
super(...args)
methods.forEach((item) => {
if (item === 'init') {
super.init( );
}
})
}
}
}
}
To expand on Paulpro's answer, since the decorator function is returning a substitute for the constructor of the class that it is decorating, it must maintain the original prototype.
In the following example, there is an error due to the missing init() method in TestDecorator<Base>.
Typescript Playground Demo
class Base {
public init() {
console.log('My base class function');
}
}
function TestDecorator<T extends Base>(methods: any[]) {
return function (target: any) {
return class extends target {
constructor(...args: any[]) {
super(...args)
methods.forEach((item) => {
if (item === 'init') {
super.init( );
}
})
}
}
}
}
#TestDecorator(['init']) // Error: Property 'init' is missing in type 'TestDecorator<Base>.(Anonymous class)' but required in type 'Child'.
class Child extends Base {
}
let c = new Child();
Corrected Decorator
function TestDecorator<T extends Base>(methods: any[]) {
return function (target: any) {
return class extends target {
init() {} // Define init()
constructor(...args: any[]) {
super(...args)
methods.forEach((item) => {
if (item === 'init') {
super.init( );
}
})
}
}
}
}
Class Decorators
If the class decorator returns a value, it will replace the class declaration with the provided constructor function.
NOTE: Should you choose to return a new constructor function, you must take care to maintain the original prototype. The logic that applies decorators at runtime will not do this for you.
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
Based on this awesome Composition over Inheritance video by MPJ, I've been trying to formulate composition in TypeScript. I want to compose classes, not objects or factory functions. Here is my effort so far (with a little help from lodash):
class Barker {
constructor(private state) {}
bark() {
console.log(`Woof, I am ${this.state.name}`);
}
}
class Driver {
constructor(private state) {}
drive() {
this.state.position = this.state.position + this.state.speed;
}
}
class Killer {
constructor(private state) {}
kill() {
console.log(`Burn the ${this.state.prey}`);
}
}
class MurderRobotDog {
constructor(private state) {
return _.assignIn(
{},
new Killer(state),
new Driver(state),
new Barker(state)
);
}
}
const metalhead = new MurderRobotDog({
name: 'Metalhead',
position: 0,
speed: 100,
prey: 'witch'
});
metalhead.bark(); // expected: "Woof, I am Metalhead"
metalhead.kill(); // expected: "Burn the witch"
This resulting in:
TS2339: Property 'bark' does not exist on type 'MurderRobotDog'
TS2339: Property 'kill' does not exist on type 'MurderRobotDog'
What's the right way of doing class composition in TypeScript?
Composition vs Inheritance
I think we should make a distinction between composition and inheritance and reconsider what we are trying to achieve. As a commenter pointed out, what MPJ does is actually an example of using mixins. This is basically a form of inheritance, adding implementation on the target object (mixing).
Multiple inheritance
I tried to come up with a neat way to do this and this is my best suggestion:
type Constructor<I extends Base> = new (...args: any[]) => I;
class Base {}
function Flies<T extends Constructor<Base>>(constructor: T = Base as any) {
return class extends constructor implements IFlies {
public fly() {
console.log("Hi, I fly!");
}
};
}
function Quacks<T extends Constructor<Base>>(constructor: T = Base as any) {
return class extends constructor implements ICanQuack {
public quack(this: IHasSound, loud: boolean) {
console.log(loud ? this.sound.toUpperCase() : this.sound);
}
};
}
interface IHasSound {
sound: string;
}
interface ICanQuack {
quack(loud: boolean): void;
}
interface IQuacks extends IHasSound, ICanQuack {}
interface IFlies {
fly(): void;
}
class MonsterDuck extends Quacks(Flies()) implements IQuacks, IFlies {
public sound = "quackly!!!";
}
class RubberDuck extends Quacks() implements IQuacks {
public sound = "quack";
}
const monsterDuck = new MonsterDuck();
monsterDuck.quack(true); // "QUACKLY!!!"
monsterDuck.fly(); // "Hi, I fly!"
const rubberDuck = new RubberDuck();
rubberDuck.quack(false); // "quack"
The benefit of using this approach is that you can allow access to certain properties of the owner object in the implementation of the inherited methods. Although a bit better naming could be use, I see this as a very potential solution.
Composition
Composition is instead of mixing the functions into the object, we set what behaviours should be contained in it instead, and then implement these as self-contained libraries inside the object.
interface IQuackBehaviour {
quack(): void;
}
interface IFlyBehaviour {
fly(): void;
}
class NormalQuack implements IQuackBehaviour {
public quack() {
console.log("quack");
}
}
class MonsterQuack implements IQuackBehaviour {
public quack() {
console.log("QUACK!!!");
}
}
class FlyWithWings implements IFlyBehaviour {
public fly() {
console.log("I am flying with wings");
}
}
class CannotFly implements IFlyBehaviour {
public fly() {
console.log("Sorry! Cannot fly");
}
}
interface IDuck {
flyBehaviour: IFlyBehaviour;
quackBehaviour: IQuackBehaviour;
}
class MonsterDuck implements IDuck {
constructor(
public flyBehaviour = new FlyWithWings(),
public quackBehaviour = new MonsterQuack()
) {}
}
class RubberDuck implements IDuck {
constructor(
public flyBehaviour = new CannotFly(),
public quackBehaviour = new NormalQuack()
) {}
}
const monsterDuck = new MonsterDuck();
monsterDuck.quackBehaviour.quack(); // "QUACK!!!"
monsterDuck.flyBehaviour.fly(); // "I am flying with wings"
const rubberDuck = new RubberDuck();
rubberDuck.quackBehaviour.quack(); // "quack"
As you can see, the practical difference is that the composites doesn't know of any properties existing on the object using it. This is probably a good thing, as it conforms to the principle of Composition over Inheritance.
Unfortunately, there is no easy way to do this. There is currently a proposal to allow for the extends keyword to allow you to do this, but it is still being talked about in this GitHub issue.
Your only other option is to use the Mixins functionality available in TypeScript, but the problem with that approach is that you have to re-define each function or method that you want to re-use from the "inherited" classes.
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