Why method added through decoration is not accessible - javascript

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.

Related

Modify Class Method Definition in TS or JS

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');

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; }

How to block TypeScript class property or method multi-level inheritance?

Here is the next JavaScript class structure:
// data.service.ts
export class DataService {
public url = environment.url;
constructor(
private uri: string,
private httpClient: HttpClient,
) { }
getAll() {}
getOne(id: number) {}
create(data: any) {}
// etc...
}
Next is the general data model what can use the DataService's methods to communicate the server:
// Model.model.ts
import './data.service';
export class Model extends DataService {
all() {}
get() {
// parse and make some basic validation on the
// DataService.getOne() JSON result
}
// etc...
}
And finally I create a specific data model based on Model.model.ts:
// User.model.ts
import './Model.model.ts';
export class User extends Model {
id: number;
name: string;
email: string;
init() {
// make specific validation on Model.get() result
}
}
If I use the User class in my code, I can call the DataService's getAll() function directly if I want. But this is not a good thing, because in this case I miss the built-in validations.
How can I block the method inheritance on a class?
I'm looking for something like PHP's static method. The child class can use the methods, but his child can't.
I want something like this:
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // undefined
model.all(); // void
const user = new User();
user.getAll(); // undefined
user.all(); // void
Is there any way to do this?
You can prevent it to be built when you call it by adding private keyword to the function private getAll() {}. But private is a TypeScript feature, not Javascript, so if you force it to be built, it is still callable. There'll be no way to totally prevent it this moment.
So if you want it to be prevented in TypeScript, just add private keyword there. But it is not buildable, not return undefined as you expect. Otherwise, just replace the function with an undefined-returned function on children classes
With your code as shown and use case as stated, the only way to get the behavior you want is not to make Model extend DataService at all. There is a subsitution principle which says that if Model extends DataService, then someone should be able to treat a Model instance exactly as they would treat a DataService instance. Indeed, if a Model is a special type of DataService, and someone asks for a DataService instance, you should be able to give them a Model. It's no fair for you to tell them that they can't call the getAll() method on it. So the inheritance tree can't work the way you have it. Instead you could do something like this:
// ultimate parent class of both DataService and Model/User
class BaseDataService {
getOne(id: number) { }
create(data: any) { }
// etc...
}
// subclass with getAll()
class DataService extends BaseDataService {
getAll() {}
}
// subclass without getAll()
class Model extends BaseDataService {
all() { }
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
That works exactly as you've specified. Perfect, right?
Well, I get the sinking feeling that you will try this and get upset that you cannot call this.getAll() inside the implementation of Model or User... probably inside the suspiciously-empty body of the all() method in Model. And already I'm getting the urge to defensively point to the Minimum, Complete, and Verifiable Example article, since the question as stated doesn't seem to require this.
If you do require that, you still can't break the substitution principle. Instead I'd suggest making getAll() a protected method, and expose an all() method on DataService:
class DataService {
getOne(id: number) { }
create(data: any) { }
protected getAll() { }
all() {
this.getAll();
}
// etc...
}
class Model extends DataService {
all() {
this.getAll();
}
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // error
dataService.all(); // okay
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
and live with the fact that getAll() is a purely internal method never meant to see the light of day.
Okay, hope one of those helps; good luck!

Proxy cannot get method in extended class

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.

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