TypeScript: Dynamically declared methods in class - javascript

I've some code like:
const methodsList = [
'foo',
'bar',
// ... 20 other items ...
]
export class Relayer {
constructor() {
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// console.log('relaying call to', methodName, args)
// this is same for all methods
}
}
}
}
const relayer = new Relayer()
relayer.foo('asd') // TS error
relayer.bar('jkl', 123) // TS error
Now when I use the class instance, TypeScript complains when I call relayer.foo() or relayer.bar(). To make the code compile, I've to cast it as any or similar.
I've an interface that declares foo, bar and the other methods:
interface MyInterface {
foo: (a: string) => Promise<string>
bar: (b: string, c: number) => Promise<string>
// ... 20 other methods
}
How do I get TypeScript to learn the dynamically declared foo and bar class methods? Can the declare syntax be useful here?

First step is to create a type or interface where when indexed by a value in methodsList, the result will be a function:
// The cast to const changes the type from `string[]` to
// `['foo', 'bar']` (An array of literal string types)
const methodsList = [
'foo',
'bar'
] as const
type HasMethods = { [k in typeof methodsList[number]]: (...args: any[]) => any }
// Or
type MethodNames = typeof methodsList[number] // "foo" | "bar"
// k is either "foo" or "bar", and obj[k] is any function
type HasMethods = { [k in MethodNames]: (...args: any[]) => any }
Then, in the constructor, to be able to assign the keys of methodsList, you can add a type assertion that this is HasMethods:
// General purpose assert function
// If before this, value had type `U`,
// afterwards the type will be `U & T`
declare function assertIs<T>(value: unknown): asserts value is T
class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
// `methodName` has type `"foo" | "bar"`, since
// it's the value of an array with literal type,
// so can index `this` in a type-safe way
this[methodName] = (...args) => {
// ...
}
}
}
}
Now after constructing, you have to cast the type still:
const relayer = new Relayer() as Relayer & HasMethods
relayer.foo('asd')
relayer.bar('jkl', 123)
You can also get rid of the casts when constructed using a factory function:
export class Relayer {
constructor() {
// As above
}
static construct(): Relayer & HasMethods {
return new Relayer() as Relayer & HasMethods
}
}
const relayer = Relayer.construct()
Another way around it is to create a new class and type-assert that new results in a HasMethods object:
class _Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
}
export const Relayer = _Relayer as _Relayer & { new (): _Relayer & HasMethods }
const relayer = new Relayer();
relayer.foo('asd')
relayer.bar('jkl', 123)
Or if you are only using new and then methods in methodsList, you can do:
export const Relayer = class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
} as { new (): HasMethods };
You can also use your MyInterface interface instead of HasMethods, skipping the first step. This also gives type-safety in your calls.

Use the following syntax:
export class Relayer {
constructor() {}
public foo(){
// your foo method
this.executedOnEachFunction();
}
public bar(){
// your bar method
this.executedOnEachFunction();
}
executedOnEachFunction(){
// what you want to do everytime
}
}
https://repl.it/repls/LawfulSurprisedMineral

To me, this sounds like a need for an interface.
interface MyInterface {
foo(): void; // or whatever signature/return type you need
bar(): void;
// ... 20 other items ...
}
export class Relayer implements MyInterface {
constructor() {}
foo(): void {
// whatever you want foo to do
}
// ... the rest of your interface implementation
}
What it looks like you are doing is implementing some interface of sorts. In your constructor you are defining what the method implementations are instead of defining them in the class body. Might help to read Class Type Interfaces

Related

Flow inheritance results in incompatible type at runtime

A.js:
// #flow
export interface A {
propA: string;
method(): void;
}
B.js:
// #flow
import { A } from "../interfaces/A.js";
export class B implements A {
propA: string;
// Additional properties here...
method() { //do stuff }
// Additional methods here...
};
main.js:
// #flow
import { A } from "../interfaces/A.js";
import { B } from "../classes/B.js";
export const processA = (w: string, obj: A): string => {
return processB(w, obj);
};
const processB = (_w: string, _obj: B): string => {
return _w;
};
Error: Cannot call 'processB' with 'obj' bound to '_obj' because 'A' [1] is incompatible with 'B' [2].
(Yes I know the A/B obj are not used in these functions, this is just a trimmed down example)
I understand why the error is being thrown, because in processB there is no guarantee that input _obj is of type B since it is of type A. But I'd like to have a method that takes in an obj: A and then passes to a submethod that requires obj to be of type B.
Is there a way to accomplish this? I'm kind of hacking around it by manually checking the constructor.name and using instanceof before calling processB, and changing declaration to const processB = (_w: string, _obj: A).
But it seems like there could be a better way. I want the initial method to take in any object that implements the interface, then have submethods that enforce the input obj to be a certain class that extends that interface.
I could only think of using instanceof since Flow needs some way of guaranteeing what type obj is. You don't need to change processB to accept an A if you're using instanceof, however. For example,
interface A {
propA: string;
method(): void;
};
class B implements A {
propA: string;
// Additional properties here...
propB: string;
method() {
// do stuff
}
// Additional methods here...
}
class C implements A {
propA: string;
// Additional properties here...
propC: string;
method() {
// do stuff
}
// Additional methods here...
}
function processA(w: string, obj: A): string {
if (obj instanceof B) {
return processB(w, obj);
} else if (obj instanceof C) {
return processC(w, obj);
}
throw new Error('Unsupported implementation of interface A');
// or just return a default string
};
function processB(w: string, obj: B): string {
return w + obj.propB;
};
function processC(w: string, obj: C): string {
return w + obj.propC;
}
Try Flow

Return constructor of generic type in TypeScript

I'm struggling to define how to write TypeScipt code which says that function return constructor of generic type. There are plenty of examples around about how to pass constructor of the generic type, but not how to return.
Please check the following example:
This is part of the abstract class:
getModel(): (new () => T) {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
When in derived class I'm trying to implement it like this:
getModel(): typeof User {
return User;
}
I have the following error:
Type '() => typeof User' is not assignable to type '() => new () => User'.
I could skip implementation in the derived class if I knew how to specify in the abstract class.
So question is - how to specify on an abstract class level that method returns constructor of the generic type and I can skip implementation of this method at child level class? Or maybe I specify return signature not correctly on the abstract class level?
EDIT:
Please check the strange problem. Class A and B differ only by the presence of explicit constructor. And in RealA doesn't work and RealB works the same getModel() method.
class A {
a = '';
constructor(a: string) {
}
}
class B {
a = '';
static test(): void {
console.log('I do work');
}
}
abstract class Base<T> {
Prop: T;
constructor(TCreator: { new (): T; }) {
this.Prop = new TCreator();
}
getModel(): (new () => T) {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
}
class RealA extends Base<A> {
getModel(): typeof A { // doesn't work - compilation error
return A;
}
}
class RealB extends Base<B> {
getModel(): typeof B { // works
return B;
}
}
var test = new RealA(A); // compile error
var test2 = new RealB(B)
For RealA class the same error
() => typeof A' is not assignable to type '() => new () => A'
The error is expected as the constructor for class A has a required argument. The abstract class constrains the constructor to be passed it to have no arguments (new () => T).
The simple solution is to remove the constructor to A.
If you want to be able to pass in classes that have constructors that require arguments you will need to change the definition of the base class to capture the constructor type, and have the constructor take in those required arguments (using tuples in rest parameters)
class A {
a = '';
constructor(a: string) {
}
}
class B {
a = '';
static test(): void {
console.log('I do work');
}
}
type ArgumentTypes<T> = T extends new (...a: infer A) => any? A : []
abstract class Base<T extends new (...a: any[])=> any> {
Prop: InstanceType<T>;
constructor(TCreator: T, ...a: ArgumentTypes<T>) {
this.Prop = new TCreator(...a);
}
getModel(): T {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
}
class RealA extends Base<typeof A> {
getModel(): typeof A { // doesn't work - compilation error
return A;
}
}
class RealB extends Base<typeof B> {
getModel(): typeof B { // works
return B;
}
}
var test = new RealA(A, ""); // ok
var test2 = new RealB(B)

Spread class functions

Is there a way to spread the functions of a class into another object? As a contrived example:
class FooBar {
private service: MyService;
constructor(svc: MyService) {
this.service = svc;
}
public foo(): string {
return "foo";
}
public bar(): string {
return "bar"
}
public fooBar(): string {
return "foobar"
}
}
let obj = new FooBar();
export default {
...obj
};
I would want the exported object to contain all the methods of the class FooBar but not the private property service. However, those methods are placed on the prototype object when compiled to javascript so they are not included in the spread operation and the private property is included on the object to it is in the resulting object.
I know I can do this:
export default {
foo: obj.foo.bind(obj),
bar: obj.bar.bind(obj),
fooBar: obj.fooBar.bind(obj),
};
I would like to avoid this if possible as I will have methods from multiple classes to map.
Note: This is to be used for combining GraphQL resolvers into a single object that will be supplied to the graphql function.
I am running my app using ts-node if that makes any difference.
I had a couple of problems going on. First, I was targeting es6 as my output instead of es5. Doing that caused there to be no prototype on the compiled object.
Second, just doing the spread caused the private property service to be included in the exported object. I ended up writing a helper function as alluded to by #Vivick and #AlekseyL.:
function combineResolvers(...resolvers: any[]): any {
let out: { [key: string]: any } = {}
resolvers.forEach(resolver => {
let proto = Object.getPrototypeOf(resolver)
Object.keys(proto)
.filter(key => {
return isFunction(resolver[key]);
}).forEach(key => {
out[key] = resolver[key].bind(resolver)
})
})
return out
}
function isFunction(functionToCheck: any): boolean {
return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}
This still has the problem of including any private functions on the resolver classes into the exported object.
I think this might work; using arrow functions for autobinding the methods and spreading it with Object.assign
class FooBar {
private service: MyService;
constructor(svc: MyService) {
this.service = svc;
}
public foo = (): string => {
return "foo";
}
public bar = (): string => {
return "bar";
}
public fooBar = (): string => {
return "foobar";
}
}
export default { ...Object.assign(new FooBar()) };
But maybe you would want to take a look at this before doing that
https://www.charpeni.com/blog/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think

Javascript nameof object property

In C# there is the posibility to get the name of an object property as a string value.
nameof(object.myProperty) --> "myProprty"
Can this be done in Javascript/Typescript?
Object.Keys() is the only thing i found, but it gives me all the keys.
Example what I want to achieve:
export interface myObject {
myProperty: string;
}
console.log(myObject["myProperty"]);
Let's say I change my interface for some reason to:
export interface myObject {
myPropertyOther: string;
}
console.log(myObject["myProperty"]); // This will not give an error on build
So I want to have something like this:
console.log(myObject[nameOf(myObject.myProperty)]
// This will give an error on build when the interface is changed
There is no nameof operator in Javascript/Typescript. You can creat a function that takes the key of another object and this is checked by the typescript compiler:
function keyOf<T>(o: T, k: keyof T) {
return k
}
let o = { a: 1 }
keyOf(o, 'a'); //ok
keyOf(o, 'b'); //err
I made a library that fetches the name of a property at runtime, even for types that don't exist at runtime (interfaces or types in TypeScript):
It can be found on NPM here: #fluffy-spoon/name-of
The source code is simple (just a few lines of code): https://github.com/ffMathy/FluffySpoon.JavaScript.NameOf
Usage
import { getPropertyName, getDeepPropertyName } from '#fluffy-spoon/name-of';
interface SomeType {
foo: boolean;
someNestedObject: {
bar: string;
}
}
console.log(getPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "bar"
console.log(getDeepPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "someNestedObject.bar"
Library source code
In case you don't want to install the NPM package.
function getPropertyNameInternal<T = unknown>(expression: (instance: T) => any, options: {
isDeep: boolean
}) {
let propertyThatWasAccessed = "";
var proxy: any = new Proxy({} as any, {
get: function(_: any, prop: any) {
if(options.isDeep) {
if(propertyThatWasAccessed)
propertyThatWasAccessed += ".";
propertyThatWasAccessed += prop;
} else {
propertyThatWasAccessed = prop;
}
return proxy;
}
});
expression(proxy);
return propertyThatWasAccessed;
}
export function getPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: false
});
}
export function getDeepPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: true
});
}
Yes, this can be done in Javascript/Typescript. There is a typescript module.
Shamelessly copied from the manual.
nameof(console);
nameof(console.log);
nameof(console["warn"]);
Transforms to:
"console";
"log";
"warn";
There are more nice examples in the manual.
The solution for yours question:
interface IMyObject {
myPropertyOther: string;
}
let myObject: IMyObject = {
myProperty: 'Hello world'
};
console.log(myObject[nameof<IMyObject>((o) => o.myProperty)]);

how to declare member variable as extended type in TypeScript?

Is there a way to define a "member variable" as an "extension object" instead of a static type (without using an interface)?
Simply something like this pseudocode:
class Foo {
bar -> extends Rectangle;
constructor(barInstance:IRectangle){
this.bar = barInstance;
this.bar.getArea(); //<-- is code completed because interface IRectangle
// no type error
this.bar.someCustomFunction = function() {
}
}
}
instead of
class Foo {
bar: IRectangle;
//or
bar:Rectangle;
}
This way I can add properties not defined on the base class or interface without getting type errors, but also get code completion from the base class. Heh, lazy strict typing?
Intersection Types
interface IRectangle {
getArea: () => number;
}
class Foo {
bar: IRectangle & { [key: string]: any; };
constructor(barInstance:IRectangle){
this.bar = barInstance;
this.bar.getArea(); //<-- is code completed because interface IRectangle
// no type error
this.bar.someCustomFunction = function() {
}
}
}
Consider a constrained generic type parameter.
interface Base {
prop: number;
}
interface Child extends Base {
thing: string;
}
class Foo<T extends Base> {
bar: T
}
var foo = new Foo<Child>();
foo.bar.thing; // now permitted by the type checker
I'm not completely sure that I understand you, but if so then something like this:
interface IRectangle {
getArea(): void;
}
class Rectangle implements IRectangle {
getArea(): void {}
someCustomFunction(): void {}
}
class Foo<T extends IRectangle> {
bar: T;
constructor(barInstance: T){
this.bar = barInstance;
this.bar.getArea();
// no type error
if (this.bar instanceof Rectangle) {
(this.bar as any as Rectangle).someCustomFunction = function() {}
}
}
}
(code in playground)

Categories

Resources