Modify Class Method Definition in TS or JS - javascript

I have a class having structure like :
class A {
constructor() {}
myMethod() {
console.log('in my method');
}
}
I want to make a method that will accept className and methodName like :
modifyClassMethod(className, methodName)
This method should wrap the whole body of methodName specified into a callback during runtime.
So if I do,
modifyClassMethod(A, myMethod)
Now during runtime the body of myMethod of class A should get changed to
myMethod() {
nr.recordMe('here', {
console.log('in my method').
})
}
Now when I will create a new object of A class, then I will get modified value of myMethod.
How can I achieve this in TS or JS ?.

You can get help of Decorator Design Pattern. It's also called as wrapper. In Typescript you can do;
interface IClassInterface{
myMethod():void
}
class classA implements IClassInterface{
constructor() {}
myMethod() {
console.log('in my method');
}
}
class AWrapper implements IClassInterface{
constructor(private classA:IClassInterface){}
myMethod(somestring?:string) {
//this.classA.myMethod(); either do this, or do sth else
console.log("wrapper works", somestring);
this.classA.myMethod();
}
}
and when using;
let aClass = new classA();
let IClassWrapper = new AWrapper(aClass);
IClassWrapper.myMethod('here');

Related

How do you call a subclass method in the baseclass method?

I am sorry if this question is so confusing, I am going to try and be as simple as possible.
TLDR; How, in a Typescript baseclass, do you call the private method of a subclass that has to be defined in the subclass?
I have many different Typescript subclasses that require different code to do the same thing: write to a file.
90% of the base code is the same, with the specific implementation of some methods per subclass.
So I have taken the same code and put it in a base class, but that calls a method that has to be implemented on the subclass and it is preferably private.
Here is an example:
abstract class BaseClass {
constructor() { }
SetOptions() {
// Set the options
// Return the options
}
GetOptions() {
// Get the options
this.WriteStuff();
}
private WriteStuff(arg1: Item, arg2: number) {}
}
class SubClass1 extends BaseClass {
private WriteStuff(arg1: SubClass1Item, number: number) {
// Write stuff in the SubClass1 way
}
}
class SubClass2 extends BaseClass {
private WriteStuff(arg1: SubClass2Item, number: number) {
// Write stuff the SubClass2 way
}
}
SubClass1Item and SubClass2Item implement the Item interface.
All subclasses will use the same code for SetOptions and GetOptions, but WriteStuff will have individualized code in each subclass and needs to be private so it isn't called by other devs on accident.
Typescript says that I can't do this because "Types have separate declarations of a private property 'WriteStuff'". And Typescript won't let me not declare the method in the BaseClass as then I am calling a method that doesn't exist but will exist in the subclass.
Is this possible?
Private vs. Protected
private methods can only be accessed in the class itself, not in any descendants. You cannot have a private abstract method because this is an inherent contradiction -- if descendants need to implement it then it is not private.
You want to use the protected modifier. Per the docs:
The protected modifier acts much like the private modifier with the exception that members declared protected can also be accessed within deriving classes.
You want to declare in your BaseClass that all concretions must implement a WriteStuff() method. This is an abstract method meaning that it has no implementation in BaseClass and must be overwritten.
In your descendants, the implementation of WriteStuff must be protected rather than private.
Code
abstract class BaseClass {
/* .... */
protected abstract WriteStuff(arg1: Item, arg2: number): void;
}
class SubClass1 extends BaseClass {
protected WriteStuff(arg1: SubClass1Item, number: number) {
// Write stuff in the SubClass1 way
}
}
Playground Link
arg1 Type
Your classes don't all share an identical signature because they each have a different type for the first argument arg1 passed to WriteStuff. You most likely want to use a generic class based on the type of arg1 or some other value.
If SubClass1Item and SubClass2Item do not extend Item then you absolutely need a generic class because the implementations of WriteStuff in the subclasses would not be assignable to the base.
If they do extend Item then you will not get typescript errors with the current setup. However there is potential for runtime errors when you are calling this.WriteStuff from GetOptions() in BaseClass. How does the BaseClass know if the Item that it has is assignable to the Item subtype that is expected in SubClass1 or SubClass2? This is where generics can help.
We make the BaseClass a generic based on a variable ItemType. We can require that all ItemType variables extend a shared base Item, but we don't have to.
The subclasses are not themselves generic classes. They will extend a version of BaseClass with the specific ItemType that they require.
Code
abstract class BaseClass<ItemType extends Item> {
/* ... */
GetOptions(item: ItemType) {
// Get the options
this.WriteStuff(item, 5);
}
protected abstract WriteStuff(arg1: ItemType, arg2: number): void;
}
class SubClass1 extends BaseClass<SubClass1Item> {
protected WriteStuff(arg1: SubClass1Item, number: number) {
// Write stuff in the SubClass1 way
}
}
Playground Link
JavaScript lately supports private field declarations including private instance methods. There, a possible approach would provide the sub class specific implementation to the base class' super call ...
class BaseClass {
// default implementation of private instance method.
#writeStuff = () => void 0;
constructor(writeStuff) {
// at instantiation time, if appropriate, ...
if (typeof writeStuff === 'function') {
// ... overwrite the default implementation.
this.#writeStuff = writeStuff;
}
}
// prototypal method ...
getOptions(...args) {
// ... with access to a private instance method.
this.#writeStuff(...args);
}
setOptions() {/* ... */}
}
class SubClass1 extends BaseClass {
constructor() {
const writeStuff = (...args) =>
// Write stuff in the SubClass1 way
console.log(`SubClass1 [...args] : ${ [...args] }`);
super(writeStuff);
// do other things
}
}
class SubClass2 extends BaseClass {
constructor() {
const writeStuff = (...args) =>
// Write stuff in the SubClass2 way
console.log(`SubClass2 [...args] : ${ [...args] }`);
super(writeStuff);
// do other things
}
}
const objA = new SubClass1;
const objB = new SubClass2;
objA.getOptions('foo', 'bar');
objB.getOptions('baz', 'biz');
.as-console-wrapper { min-height: 100%!important; top: 0; }

Access private method in an overriden method called from the base class constructor

Consider the following code:
class MyBase {
constructor(b) {
this.myOverrideMethod(b);
}
myOverrideMethod(b) {}
}
class MyClass extends MyBase {
constructor(b) {
super(b);
}
myOverrideMethod(b) {
if (b) {
this.mySpecificMethod();
} else {
this.#myPrivateMethod();
}
}
mySpecificMethod() {
console.log('mySpecificMethod');
}
#myPrivateMethod = () => {
console.log('#myPrivateMethod');
};
}
new MyClass(true); // <-- "mySpecificMethod"
new MyClass(false); // <-- Uncaught TypeError: Cannot read private member #myPrivateMethod
// from an object whose class did not declare it
The overridden method of myOverrideMethod() is called in the constructor of the "base" class. Since this points to an instance of MyClass, the overrided method in the derived class is invoked correctly. The regular method of mySpecificMethod() gets called successfully, while invoking the private field of #myPrivateMethod() throws the following "TypeError":
Uncaught TypeError: Cannot read private member #myPrivateMethod from an object whose class did not declare it
The error message does not convey the issue as being a private field access violation, but rather evokes the this does not yet reference an instance of MyClass but still references an instance of MyBase! However, why is that and how to successfully invoke the private method of #myPrivateMethod()?
This is only an issue when trying to call the private method during the constructor. I think it's because the child class hasn't yet been fully constructed while in the parent's constructor, so the private method isn't available yet.
See the example below where I call this.myOverrideMethod() from an ordinary method, and the call is allowed.
class MyBase {
constructor() {
}
doit(b) {
this.myOverrideMethod(b);
}
myOverrideMethod(b) {}
}
class MyClass extends MyBase {
constructor() {
super();
}
myOverrideMethod(b) {
if (b) {
this.mySpecificMethod();
} else {
this.#myPrivateMethod();
}
}
mySpecificMethod() {
console.log('mySpecificMethod');
}
#myPrivateMethod = () => {
console.log('#myPrivateMethod');
};
}
new MyClass().doit(true);
new MyClass().doit(false);

How to test Typescript class members in React with Jest

I have a Typescript class (with react) with public and private member functions.
export class Myclass {
private itemList: SomeItem[];
constructor(params) {
}
public method1() {
}
private method2() {
}
}
How can I test method1 and method2 in my code using jest. I can do it for functions who are exported and are not members of a class. But how can I do it for class members.
First, you need an instance...
const instance = new MyClass()
Then, you can test method1 by calling it directly...
expect(instance.method1()).toBe(...)
For method2, you've got 3 options...
Use #ts-ignore:
// #ts-ignore
expect(instance.method2()).toBe(...)
Cast as any:
expect((instance as any).method2()).toBe(...)
// no type safety on method2
Change to protected and extend:
class MyClass {
protected method2() {}
}
class MyClassTester extends MyClass {
public runTests() {
expect(this.method2()).toBe(...)
}
// OR
public method2Accessor(...args: any[]) {
return this.method2(...args)
}
}
const instance = new MyClassTester()
instance.runTests()
// OR
expect(instance.method2Accessor()).toBe(...)
// fully type-safe
You have to create an instance of the class and then call its public methods in your test. There is no way around it.

Why method added through decoration is not accessible

Suppose that I have a Directive decorator which adds a static method to it's target called factory:
function Directive<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
static factory(...args): ng.IDirectiveFactory {
const c: any = () => {
return constructor.apply(this, args);
};
c.prototype = constructor.prototype;
return new c(...args);
}
};
}
I also add the type via an interface:
interface BaseDirective extends ng.IDirective {
factory(): ng.IDirectiveFactory;
}
Why in my class declaration of:
#Directive
class FocusedDirective implements BaseDirective {....
I get a Class 'FocusedDirective' incorrectly implements interface 'BaseDirective'.
Property 'factory' is missing in type 'FocusedDirective'.
Am I wrong to expect from #Directive to add this missing property for me?
Decorators can't change the type of the class, you can invoke your decorator as a function and store the new class which will contain the method and use the new class instead of the original:
const FocusedDirectiveWithDirective = Directive(FocusedDirective);
You can do away with the intermediate class altogether by using class expressions:
const FocusedDirective = Directive(class implements BaseDirective{
});
You have two problems. The first has little to do with decorators: factory is a static method in your implementation, but a regular method in your interface:
interface BaseDirective {
factory(): ng.IDirectiveFactory;
}
That's going to be a problem for you. For now I'm going to convert the implementation to a regular method, since it's simpler to implement.
function Directive<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
factory(...args: any[]): ng.IDirectiveFactory {
const c: any = () => {
return constructor.apply(this, args);
};
c.prototype = constructor.prototype;
return new c(...args);
}
};
}
The second issue: decorators do not mutate the class signature the way you're expecting. This is an oft-requested feature and there are some interesting issues around why it's not a simple problem to solve. Importantly, it's not easy to figure out how to support having the implementation of the class refer to the mutated type. In your case: would the stuff inside your {.... know about factory() or not? Most people seem to expect it would, but the decorator hasn't been applied yet.
The workaround is not to use decorator syntax at all, but instead to use the decorator function as a regular function to create a new class. The syntax looks like this:
class FocusedDirective extends Directive(class {
// any impl that doesn't rely on factory
prop: string = "hey";
foo() {
this.prop; // okay
// this.factory(); // not okay
}
}) implements BaseDirective {
// any impl that relies on factory
bar() {
this.prop; // okay
this.foo(); // okay
this.factory(); // okay
}
}
This also solves the "does the implementation know about the decorator" issue, since the stuff inside the decorator function does not, and the stuff outside does, as you see above.
Back to the static/instance issue. If you want to enforce a constraint on the static side of a class, you can't do it by having the class implement anything. Instead, you need to enforce the static side on the class constructor itself. Like this:
interface BaseDirective {
// any actual instance stuff here
}
interface BaseDirectiveConstructor {
new(...args: any[]): BaseDirective;
factory(): ng.IDirectiveFactory;
}
class FocusedDirective extends Directive(class {
// impl without BaseDirectiveConstructor
}) implements BaseDirective {
// impl with BaseDirectiveConstructor
bar() {
FocusedDirective.factory(); // okay
}
}
function ensureBaseDirectiveConstructor<T extends BaseDirectiveConstructor>(t: T): void {}
ensureBaseDirectiveConstructor(FocusedDirective);
The ensureBaseDirectiveConstructor() function makes sure that the FocusedDirective class constructor has a static factory() method of the right type. That's where you'd see an error if you failed to implement the static side.
Okay, hope that helps. Good luck.

Static function inheritance

I've created a few classes as Controllers for my routes in Node.js and I'd like them to be able to work on a singleton basis (not required, it's an extra)
Main Controller class
class RouteController {
static getInstance() {
if (!RouteController.singleton) {
RouteController.singleton = new RouteController();
}
return RouteController.singleton;
}
}
Now I want to create a route for example viewing posts and I want that class to have the singleton pattern as well, but not cop
PostController class
class PostController extends RouteController {
}
If I do this and check it console.log(RouteController.getInstance() === PostController.getInstance()) it returns true when I want each (sub)class to have their own singleton instance.
How can I do this correctly?
One simple way would be to see if your singleton property is an instance of this. This will work even if you call RouteController.getInstance() before doing so on any of the derived classes.
class RouteController {
log() {
return 'Route';
}
static getInstance() {
if (!(this.singleton instanceof this)) {
// Only called twice so you can verify it's only initialized
// once for each class
console.log('Creating singleton...');
this.singleton = new this();
}
return this.singleton;
}
}
class PostController extends RouteController {
log() {
return 'Post';
}
}
console.log(RouteController.getInstance().log());
console.log(PostController.getInstance().log());
console.log(RouteController.getInstance().log());
console.log(PostController.getInstance().log());
The normal rules of functions apply. Hence you can use this inside the function to refer to the object the method was invoked on (the constructor function itself in this case):
class RouteController {
static getInstance() {
if (!this.singleton) {
this.singleton = new this();
}
return this.singleton;
}
}
However, this will still not work if RouteController.getInstance() is called directly, because then all subclasses inherit a static singleton property, causing the !this.singleton test to fail for each subclass.
There are multiple ways to solve this. One would be to use getOwnProperty instead of accessing it directly:
if (!this.getOwnProperty('singleton')) {
}

Categories

Resources