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')) {
}
Related
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; }
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);
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.
I am trying to use a Proxy, and I am having issues. I have a class like so:
export class Builder {
public doSomething(...args: (string | number | Raw | Object)[]): this {
// Do stuff
return this
}
}
export class ModelBase extends Builder {
protected _items = {}
}
export class Model extends ModelBase {
public constructor(options?: ModelSettings) {
super(options)
return new Proxy(this, {
get: function (target, property) {
return target._items[property] || target
}
})
}
public static create() {
return new this()
}
}
I then extend Model like so:
export class MyClass extends Model {
public constructor() {
super({/* Some options go here */})
// Do some stuff
}
public static getItems() {
let t = this.create()
t.doSomething()
}
}
Then I call getItems() which creates an instance of the class.
MyClass.getItems()
When I run this, I get the error:
TypeError: t.doSomething is not a function
Where doSomething() is within the class ModelBase. If I comment out the Proxy things work as usual. So, I would like to know why I can't access the parent class.
Your proxy is trying to find target._items.doSomething, but that doesn't exist. target.doSomething does exist, but it's not looking for that. As a result, it falls back to returning target, which is the object as a whole. The object, is not a function, and thus the error.
As for how to fix this, that depends on what you're trying to achieve with the proxy. If there's a limited set of properties that the proxy should be paying attention to, you could check those explicitly. Or more generally you might be able to check whether target[property] exists, then fall back to target._items[property] and only then fall back to target. Again, it depends on what you're trying to achieve.
I am removing decorators from my React Native app (too many issues with babel) and my actions are not working (the contained function does not run).
I'm translating class actions e.g.
class MyStore {
//...
#action
myAction(param) {
//...
}
}
To
class MyStore {
//...
myAction(param) {
action("Perform action with param", (param) => {
//...
})
}
}
What's the correct way to convert a class #action to the non-decorator form?
You can define action as
class MyStore {
//...
myAction = action(param => {
//...
});
}
or use runInAction()
class MyStore {
//...
myAction(param) {
runInAction(() => {
//...
})
}
}
What's the correct way to convert a class #action to the non-decorator form?
Decorators evaluate to function calls at runtime, so simply calling them manually would be the most straightforward thing to do. #action is a method decorator, and method decorators take the following arguments at runtime:
The class prototype for instance methods (constructor function for static methods)
The method name (property key)
The property descriptor of the method
With that in mind, you can simply do:
class MyStore {
myAction(param) {
// ...
}
}
// Apply the #action decorator manually:
action(MyStore.prototype, "myAction");
or:
action(MyStore.prototype, "myAction", Object.getOwnPropertyDescriptor(MyStore.prototype, "myAction"));
If you do this immediately after the class declaration, the result should be completely identical to that by using decorators, without having to use the decorator syntax.