I was wondering if its possible to list callbacks in a class?
I already tried the answers listed here: Get functions (methods) of a class
But they do not list callbacks.
export default class Foo {
public myCallback: () => void;
private readonly bar: any;
constructor() {
console.log(Object.getOwnPropertyNames(this).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(this))));
// [ 'bar', 'constructor', 'biz' ]
// 'myCallback' is not listed.
}
public biz() {
}
}
That is because of the way the javascript code is generated by the compiler.
Since the myCallback property is never set it optimizes the code and outputs nothing in javascript.
var Foo = (function() {
function Foo() {
// no my callback property is generated since it's never used
console.log(Object.getOwnPropertyNames(this).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(this))));
}
Foo.prototype.biz = function() {};
return Foo;
}());
However if at runtime you actually set that property it will be present. Like this.
class Foo {
public myCallback: () => void;
private readonly bar: any;
constructor() {
}
public logMethods() {
var props = [];
let obj = this;
do {
props = props.concat(Object.getOwnPropertyNames(obj));
} while (obj = Object.getPrototypeOf(obj));
props.forEach(method => console.log(method));
}
}
let a = new Foo();
a.myCallback = () => console.log('abc');
a.logMethods();
You can see the working example here.
Related
I'm trying to create a class which allows passing a callback to alter the side-effects of a method. If you don't pass a callback, then the method will be called directly. This is a basic example:
class Button<T = void> {
private clickWrapper?: (click: Function) => T
private _click() {
// do the click here
return null;
}
constructor(clickWrapper?: (click: Function) => T) {
this.clickWrapper = clickWrapper;
}
public click() {
if (this.clickWrapper) {
return this.clickWrapper(this._click.bind(this));
} else {
return this._click();
}
}
}
class Foo {
public doStuff() {
console.log('hello');
}
}
const button = new Button<Foo>(click => {
// do some stuff
click();
return new Foo();
});
const foo = button.click();
foo.doStuff();
const button2 = new Button();
button2.click();
This works, but foo.doStuff() complains that foo may be null - even though in this case I provided a clickWrapper, so the return value of button.click() cannot be null, it must be an instance of Foo. Is there a better way to define this?
The second issue is I have to copy the Button constructor's parameter type when I've already declared it for Button.clickWrapper. How do I avoid having to declare the type on the private property and constructor parameter?
I have updated you code snippet:
class Button<T = null> {
constructor(private clickWrapper?: (click: Function) => T) {}
private _click() {
// do the click here
return null;
}
public click(): T {
if (this.clickWrapper) {
return this.clickWrapper(this._click.bind(this));
} else {
return this._click();
}
}
}
class Foo {
public doStuff() {
console.log("hello");
}
}
const button = new Button<Foo>(click => {
// do some stuff
click();
return new Foo();
});
const foo = button.click();
foo.doStuff();
const button2 = new Button();
button2.click();
Two things:
TypeScript can't be sure what is exact return type of your public click function so it assumes T | null, since default _click function returns null
To avoid redeclaring types for constructor and property of an object, you can always use shorthand syntax for constructor assignment (just add private or public keyword to constructor param)
interface Callback<V> {
(arg: () => void): V
}
class Button<T = void> {
constructor(private callback?: Callback<T>) {}
private onClick = () => {
}
public click = () => {
if (this.callback) {
return this.callback(this.onClick)
} else {
return this.onClick()
}
}
}
const button = new Button<number>(
click => {
click()
return 2 +2
}
)
console.log(button.click()) // 4
I update your code to solve your problems
Create an interface for the callback type and add the private callback? to the constructor to inject the argument to the class
There are many types for a function, in typescript a function that not return nothing is a void function, you are returning null, so that didn't match with your clickWrapper type, I assume you aren't gonna return anything from the click function so I update that type to match too with a void function
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
I have this simple class:
class Foo {
constructor() {
this.init();
return this;
}
init() {
this.onInit();
}
onInit(callback) {
this.onInit = () => callback();
return this;
}
}
new Foo().onInit(() => console.log('baz'));
It's obviously flawed, because it will call init before the onInit method is able to define the onInit property/callback.
How can I make this work without change the interface?
How can I make this work without change the interface?
You can't, the interface is inherently flawed. That's really the answer to your question.
Continuing, though, with "what can I do instead":
If you need to have a callback called during initialization, you need to pass it to the constructor, not separately to the onInit method.
class Foo {
constructor(callback) {
this.onInit = () => {
callback(); // Call the callback
return this; // Chaining seemed important in your code, so...
};
// Note: Constructors don't return anything
}
}
new Foo(() => console.log('baz'));
In a comment you've said:
I see your point, the fact is that my library is new Something().onCreate().onUpdate()
It sounds like you might want to adopt the builder pattern instead:
class Foo {
constructor(callbacks) {
// ...use the callbacks here...
}
// ...
}
Foo.Builder = class {
constructor() {
this.callbacks = {};
}
onCreate(callback) {
this.callbacks.onCreate = callback;
}
onUpdate(callback) {
this.callbacks.onUpdate = callback;
}
// ...
build() {
// Validity checks here, do we have all necessary callbacks?
// Then:
return new Foo(this.callbacks);
}
};
let f = new Foo.Builder().onCreate(() => { /*...*/}).onUpdate(() => { /*... */}).build();
...although to be fair, a lot of the advantages (though not all) of the builder pattern can be realized in JavaScript by just passing an object into constructor directly and doing your validation there, e.g.:
let f = new Foo({
onCreate: () => { /*...*/},
onUpdate: () => { /*...*/}
});
Assuming that onInit is supposed to be some sort of hook to be called synchronously whenever an object is instantiated, you can't solve this on the instance level.
You can make onInit a static function, like so:
class Foo {
constructor() {
// whatever
Foo.onInit();
}
static onInit() {} // empty default
}
Foo.onInit = () => console.log('baz'); // Override default with your own function
const f = new Foo();
const f2 = new Foo();
I'm trying to develop a decorator for REST Api Interfaces in Typescript. Here it is the decorator implementation
export function RemoteResource(params: any): Function {
console.log("RemoteResource.params: ", params);
return function (target: Function) {
//--POST
target.prototype.post = function () {
console.log("----POST");
};
//--GET
target.prototype.retrieve = function () {
console.log("----GET");
};
//--DELETE
target.prototype.remove = function () {
console.log("----DELETE");
};
//--PULL
target.prototype.update = function () {
console.log("----PULL");
};
console.log("RemoteResource.target: ", target);
return target;
}
}
Now, I can use the decorator #RemoteResource and the methods post|retrieve|remove|update are added to the original object prototype correctly.
#RemoteResource({
path: "/foos",
methods: [],
requireAuth: false
})
export class Foo { }
From here, if I execute
let tester = new Foo();
tester.post() //--This prints out "----POST" correctly
I've the log printed out correctly, but I've also have the following error: "Property 'post' does not exist on type 'Foo'."
While I understand why I'm having this error (Foo doesn't have any declared post property) I'm not sure about how to fix it.
Ideally, I would like that the TS compiler understand that the decorator extends the original object adding up those methods.
How can I achieve it? Any ideas?
Thanks!
Since you are adding these methods dynamically at runtime in the decorator, the compiler has no way of knowing that these methods will exist for Foo instances.
You can change that in different ways, for example:
(1) Using an interface and intersection:
interface RemoteResource {
post(): void;
remove(): void;
update(): void;
retrieve(): void;
}
let tester = new Foo() as Foo & RemoteResource;
tester.post(); // no error
(2) Interface and empty methods:
export class Foo implements RemoteResource {
post: () => void;
remove: () => void;
update: () => void;
retrieve: () => void;
}
let tester = new Foo() as Foo & RemoteResource;
tester.post();
Edit
#Robba suggests:
(3) Ignore all type checking
let tester = new Foo() as any;
tester.post();
or
let tester = new Foo();
tester["post"]();
Is there a way to nest classes in TypeScript. E.g. I'd like to use them like:
var foo = new Foo();
var bar = new Foo.Bar();
In modern TypeScript we have class expressions which you can use to create a nested class. For example you can do the following :
class Foo {
static Bar = class {
}
}
// works!
var foo = new Foo();
var bar = new Foo.Bar();
Here is a more complex use case using class expressions.
It allows the inner class to access the private members of the outer class.
class classX {
private y: number = 0;
public getY(): number { return this.y; }
public utilities = new class {
constructor(public superThis: classX) {
}
public testSetOuterPrivate(target: number) {
this.superThis.y = target;
}
}(this);
}
const x1: classX = new classX();
alert(x1.getY());
x1.utilities.testSetOuterPrivate(4);
alert(x1.getY());
codepen
I couldn't get this to work with exported classes without receiving a compile error, instead I used namespaces:
namespace MyNamespace {
export class Foo { }
}
namespace MyNamespace.Foo {
export class Bar { }
}
If you're in the context of a type declaration file, you can do this by mixing classes and namespaces:
// foo.d.ts
declare class Foo {
constructor();
fooMethod(): any;
}
declare namespace Foo {
class Bar {
constructor();
barMethod(): any;
}
}
// ...elsewhere
const foo = new Foo();
const bar = new Foo.Bar();
This answer is about a seemless nested class implementation in TypeScript which builds on top of #basarat 's answer.
To make the type of the static nested class Bar accessible (as #PeterMoore pointed out), declare the type of the nested class in a namespace. That way, we can use the shortcut Foo.Bar. By moving type typeof Foo.Bar.prototype into a type in a declared namespace, we do not have to repeat the expression.
class Foo {
static Bar = class {
}
}
declare namespace Foo {
type Bar = typeof Foo.Bar.prototype
}
// Now we are able to use `Foo.Bar` as a type
let bar: Foo.Bar = new Foo.Bar()
For static classes, the following implementation might be more elegant. This, however does not work with non-static classes.
class Foo { }
namespace Foo {
export class Bar { }
}
let bar: Foo.Bar = new Foo.Bar()
To export the class, an export statement can be added after the class and the namespace have been declared, e.g. export default Foo or export { Foo }.
To achieve the same with a non-static nested class, see the following example.
class Foo {
Bar = class {
}
}
declare namespace Foo.prototype {
type Bar = typeof Foo.prototype.Bar.prototype
}
let foo: Foo = new Foo()
let bar: Foo.prototype.Bar = new foo.Bar()
I Hope this can be helpful
Able to:
Create a new inner class instance
Access outer class instance/prototype members
Implement interfaces
Use decorators
Use Case
export interface Constructor<T> {
new(...args: any[]): T;
}
export interface Testable {
test(): void;
}
export function LogClassName<T>() {
return function (target: Constructor<T>) {
console.log(target.name);
}
}
class OuterClass {
private _prop1: string;
constructor(prop1: string) {
this._prop1 = prop1;
}
private method1(): string {
return 'private outer method 1';
}
public InnerClass = (
() => {
const $outer = this;
#LogClassName()
class InnerClass implements Testable {
private readonly _$outer: typeof $outer;
constructor(public innerProp1: string) {
this._$outer = $outer;
}
public test(): void {
console.log('test()');
}
public outerPrivateProp1(): string {
return this._$outer._prop1;
}
public outerPrivateMethod1(): string {
return this._$outer.method1();
}
}
return InnerClass;
}
)();
}
const outer = new OuterClass('outer prop 1')
const inner = new outer.InnerClass('inner prop 1');
console.log(inner instanceof outer.InnerClass); // true
console.log(inner.innerProp1); // inner prop 1
console.log(inner.outerPrivateProp1()); // outer prop 1
console.log(inner.outerPrivateMethod1()); // private outer method 1