The problem is pretty simple I think.
I have my custom decorator which need some object to proper running.
This object is some service which I have provided for my component. So that I want to pass this service into the decorator.
This is some example of code:
#Component({...})
export class TestComponent {
constructor(private service: TestService){}
#MyDecorator(service)
run(){
...
}
}
Obviously, this approach is bad because the service isn't accessible at the moment when is passed in MyDecorator.
What should I do?
I know I'm a little bit late, but i solved the problem by wrapping it in a function.
// OBJECT THAT WE WILL PASS
class SomeKindOfObject {}
// DECORATOR
function Decorator(type: ()=>object){
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target, propertyKey, descriptor);
};
}
// CLASS WRAPPED WITH FUNCTION
function component<T extends object>(classRef: T){
class Component{
#Decorator( () => classRef )
find() {
console.log(classRef);
}
}
return Component;
}
// CLASS USAGE
const comp = new (component(SomeKindOfObject));
comp.find();
// INHERITANCE USAGE
class Component2 extends component(SomeKindOfObject){}
Note - i didn't make this example specifically for angular, but i was having a similar problem in nestjs, hope this helps someone.
Related
This is something that if I'm able to achieve will be able to design a very easily extendible decorator factory for a project I'm working on.
Basically, I want to be able to use a super class's methods as a decorator for the sub-classes property.
This is usually how decorators work:
import { handleSavePropertyNameDecorator } from "./W-ODecorator"
class Main {
#handleSavePropertyNameDecorator
test1: string = ""
}
export default Main
And then the decorator function:
const propertyNames = [];
export const handleSavePropertyNameDecorator = (_instance: any, propertyName: string) => {
console.log("Executing handleSavePropertyNameDecorator")
propertyNames.push(propertyName);
}
However, instead of defining a separate function for the decorator, I'd like the decorator function to come from the super class:
import SuperClass from "./SuperClass"
class Main extends SuperClass{
#this.handleDecoratorFunction
test1: string = ""
}
export default Main
class SuperClass {
static propertyNameArray: string[] = [];
protected handleDecoratorFunction(_instance: any, propertyName: string) {
console.log("executing handle decorator function from super class!")
SuperClass.propertyNameArray.push(propertyName);
}
}
export default SuperClass;
Currently the keyword "this" has a compilation error that states it's possibly undefined. I've run this code and it doesn't execute the decorator function.
My question is this approach possible with some type of workaround? If this is possible that will significantly improve my organization.
Thank you in advance!
No, you can't do that, the decorator is applied when the class is defined not when it's instantiated. There are no instance methods from your superclass available.
However, you can do this with a static method of course:
class SuperClass {
static propertyNameArray: string[] = [];
protected static handleDecoratorFunction(_instance: any, propertyName: string) {
console.log("executing handle decorator function from super class!")
this.propertyNameArray.push(propertyName);
}
}
class Main extends SuperClass{
#SuperClass.handleDecoratorFunction
test1: string = ""
}
export default Main
I get a circular dependency on my decorators because my class ThingA has a relation with ThingB and vice versa.
I've read several questions about this issue:
Beautiful fix for circular dependecies problem in Javascript / Typescript
TypeScript Decorators and Circular Dependencies
But I wasn't able to find a valid solution for my case.
I tried as many people suggest to change from #hasOne(ThingA) to #hasOne(() => ThingA) to force a lazy loading and break the dependency, but this solution doesn't work because I'm not able to get the constructor name.
I need the name of the constructor (example: 'ThingA') to add it in metadata of constructor ThingB.
Following my original code (without lazyload modification)
ThingA
#hasAtLeast(0, ThingB)
export class ThingA extends RTContent {
#IsEmail()
email: string;
}
ThingB
#hasOne(ThingA)
export class ThingB extends RTContent {
#IsString()
description: string;
}
Decorators:
export type LinkConstraint<C extends RTContent> = {
content: string; // name of the constructor (ex. 'ThingA')
maxOccurrences: number;
minOccurrences: number;
constructor: { new(...args: any[]): C };
}
function constraintFactory<C extends RTContent>(minOccurrences: number, maxOccurrences: number, associated: { new(...args: any[]): C }) {
return (constructor: Function) => {
const constraints = Reflect.getMetadata('linkConstraints', constructor) || [];
const constraint: LinkConstraint<C> = {
content: associated?.name,
minOccurrences,
maxOccurrences,
constructor: associated
};
constraints.push(constraint);
Reflect.defineMetadata('linkConstraints', constraints, constructor)
}
}
export function hasOne<C extends RTContent>(associated: { new(...args: any[]): C }) {
return constraintFactory(1, 1, associated)
}
export function hasAtLeast<C extends RTContent>(minOccurrences: number, associated: { new(...args: any[]): C }) {
return constraintFactory(minOccurrences, Infinity, associated)
}
I see that your decorator doesn't actually modify the constructor, it merely runs some side-effect code to add some metadata entry. Thus the #decorator syntax isn't a must.
My advice is that you decorate neither ThingA nor ThingB, just export them as-is. You defer the decoration in another module, which should be the common parent of both ThingA and ThingB. This way circular dependency is resolved.
For example, in './things/index.ts' you do:
import { ThingA } from './things/ThingA';
import { ThingB } from './things/ThingB';
hasOne(ThingA)(ThingB);
hasAtLeast(0, ThingB)(ThingA);
export { ThingA, ThingB }
Now other part of your code can import from './things/index.ts', instead of directly from './things/ThingA(or B).ts'. This would ensure the decoration is executed before instantiation of classes.
If you must use decorator, well lazy load is your best bet. #hasOne(() => ThingA) should does the trick, but you need to modify the implementation of hasOne accordingly, a little hack.
function hasOne(target) {
if (typeof target === "function") {
setTimeout(() => {
_workOnTarget(target())
}, 0)
} else {
_workOnTarget(target)
}
}
The key is to delay accessing variable value.
For this hack to work, we still rely on the fact that these decorators are side-effect only, don’t modify the constructor. So this is NOT a general solution to circular dependency problem. More general pattern is off course lazy evaluation. More complicated though, if you really need, pls ask in comments.
For your case, above impl should work. But you must not instantiate ThingA or B right inside any module’s top level, cuz that would happen before setTimeout callback, thus break the hack.
I have a base class that I am trying to extend:
export class BaseClass<T extends SomeOtherClass> {
constructor(param: ParamType) {
}
doSomething(param1: Param1Type): BaseClass<T> {
// do something with param1;
return this;
}
}
My class:
export class MyClass<T extends SomeOtherClass> extends BaseClass<T> {
constructor(param: ParamType) {
super(param);
}
doSomething(param1: Param1Type, param2: Param2Type): MyClass<T> {
// super.doSomething(param1);
// do something with param2;
return this;
}
}
but I'm getting a warning:
Property 'doSomething' in type 'MyClass<T>' is not assignable to the same property in base type 'BaseClass<T>'.
Type '(param1: Param1Type, param2: Param2Type) => MyClass<T>' is not assignable to type '(param1: Param1Type) => BaseClass<T>'.
Is it not possible to extend method signatures in typescript? How do I extend the capabilities of the BaseClass if I need to add a parameter to the overridden method and is this the correct way of calling the parent method in es6 syntax. I'm aware that prior to es6 I could have called BaseClass.prototype.doSomething.call(this, param1).
The problem as pointed out by others is that if param2 is required it breaks polymorphism:
// We should be able to do this assignment
let baseRef: BaseClass<SomeOtherClass> = new MyClass<SomeOtherClass>("");
baseRef.doSomething("") // param2 is not required by the base class so MyClass will not receive it even though it NEEDS it
One solution, is to make the second parameter optional, so the call baseRef.doSomething("") is valid for the derived type as well :
export class MyClass<T extends SomeOtherClass> extends BaseClass<T> {
constructor(param: string) {
super(param);
}
doSomething(param1: string, param2?: string): MyClass<T> {
super.doSomething(param1);
return this;
}
}
A second solution, if we only want to share code between the classes, is to disallow the assignment let baseRef: BaseClass<SomeOtherClass> = new MyClass<SomeOtherClass>(""); by not really inheriting BaseClass but rather inherit a class that excludes the doSomething method:
type PartialBaseClass = new <T> (param: string) => { [P in Exclude<keyof BaseClass<T>, 'doSomething'>] : BaseClass<T>[P] }
const PartialBaseClass:PartialBaseClass = BaseClass
export class MyClass<T extends SomeOtherClass> extends PartialBaseClass<T> {
constructor(param: string) {
super(param);
}
doSomething(param1: string, param2: string): MyClass<T> {
BaseClass.prototype.doSomething.call(this, param1);
return this;
}
}
// This is now invalid !
let baseRef: BaseClass<SomeOtherClass> = new MyClass<SomeOtherClass>("") ;
This is a violation of OOP as it would break polymorphism. An example where you might typically use this could be with a base class Filter and a derived class FilterWithDelay. The parent class implements a method called setFilter. In OOP you might have an array of Filters which contains instances of both Filter and FilterWithDelay. You might typically want to iterate through the array and call setFilter on each object. For this to be possible the child method should implement the same function signature as the parent method. Otherwise the code, would need to check each instance to see if its a Filter or FilterWithDelay in order to pass in the additional parameters.
In order to implement the FilterWithDelay you can extend the parent class and pass the delay as a parameter to the constructor instead of the method. This way setFilter can implement the common interface.
export class BaseClass<T extends SomeOtherClass> {
constructor(param: ParamType) {
}
doSomething(param1: Param1Type): BaseClass<T> {
// do something with param1;
return this;
}
}
export class MyClass<T extends SomeOtherClass> extends BaseClass<T> {
constructor(param: ParamType, param2: Param2Type) {
super(param);
this.param2 = param2;
}
doSomething(param1: Param1Type): MyClass<T> {
return super.doSomething(param1 + this.param2);
}
}
My TypeScript v2.2.
I have this class factory:
export class A { name: string; }
export function makeConstructor(name: string)
{
const newClass = class extends A { };
newClass.prototype.name = name;
return newClass;
}
TypeScript throw error:
Return type of exported function has or is using private name '(Anonymous class)'.
I can say that this factory returns any to hide error, but how I can explain what exactly returns?
I tried to write
makeConstructor<T extends A>(name: string): T
makeConstructor<T extends typeof A>(name: string): T
makeConstructor<T extends A['prototype']>(name: string): T['prototype']
When you use the Factory design pattern, you should probably want to shield the exact implementation class chosen by the Factory, and just return A (or its interface) instead. Therefore, I think returning A just does the trick, no need for generics etc...
So I'm trying to extend a class in node js and the compiler keeps returning the following error:
TypeError: Class extends value #<Object> is not a function or null
I checked that I was exporting the class correctly and I am, any ideas? I'll post my code below:
/handler/venue.js:
var VenueViews = require('../views/venue'); // If I remove this the error will dissapear (as expected)
class Venue {
constructor(data) {
this.setDataHere = data;
}
main () {
var View = new VenueViews(); // This doesn't run
}
}
module.exports = Venue;
/views/venue.js:
var Venue = require('../handlers/venue');
console.log (Venue) // This returns {} ???
class VenueViews extends Venue {
constructor() {
super();
}
}
module.exports = VenueViews;
I know that node supports these es6 features, so I'm unsure why they aren't working?
Edit:
I'm not sure if this is suppose to happen but, when I log my Venue require it returns an empty object {}.
console.log (Venue) // This returns {} ???
So it turns out I had a circular reference in my code, where I was importing the class that was extending, into the class that itself was extending (tongue twister :P).
The obvious fix was to simply remove the extends reference and find another way of doing what I was trying to achieve. In my case it was passing the Venue class properties down into the VenueViews constructor.
E.g var x = VenueViews(this)
In my instance, it was the same issue as #James111 was experiencing (circular import) due to a factory pattern I was trying to set up in Typescript. My fix was to do move the code into files, similar to the following:
// ./src/interface.ts
import { ConcreteClass } from './concrete';
export interface BaseInterface {
someFunction(): any;
}
export class Factory {
static build(): BaseInterface {
return new ConcreteClass();
}
}
// ./src/base.ts
import { BaseInterface } from './interface';
class BaseClass implements BaseInterface {
someFunction(): any {
return true;
}
}
// ./src/concrete.ts
import { BaseClass } from './base';
export class ConcreteClass extends BaseClass {
someFunction(): any {
return false;
}
}
I had faced similar issue, after checking all the workaround finally issue got resolved by deleting the node_modules folder and run npm i.