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...
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 am trying to learn strict typing in Typescript.
I defined these classes:
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
private param: TParam;
setInitParams(init: TParam): void {
...
}
getInitParams(): TParam {
....
}
}
export class MyClass extends MyAbstractClass<AParamType> {
private param: AParamType;
...
}
The problem is that I get the error " Class 'MyClass' incorrectly extends base class 'MyAbstractClass'.
Types have separate declarations of a private property 'param'."
I don't understand why I get this error because AParamType type correctly extends MyParamBaseType
Can somebody helps me ? Thanks for your help.
The private keyword is just a compile time checked. This means that the field param will be stored at runtime in the instance of the class. MyAbstractClass declares it's member private, so if MyClass were allowed to redeclare the param field it would end up accessing the same field named param in the instance at runtime breaking privacy.
You can use the ES private class fields (with #). These ensure hard privacy even at runtime and name collisions in the sub class are not an issue since each declared field is distinct even if they share the same name:
type MyParamBaseType = {}
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
#param!: TParam;
setInitParams(init: TParam): void {
}
getInitParams(): TParam {
return this.#param;
}
}
type AParamType = {}
export class MyClass extends MyAbstractClass<AParamType> {
#param!: AParamType;
}
Playground Link
Or if you want to access the same field from the base class you might consider protected instead:
type MyParamBaseType = {}
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
protected param!: TParam;
setInitParams(init: TParam): void {
}
getInitParams(): TParam {
return this.param
}
}
type AParamType = {}
export class MyClass extends MyAbstractClass<AParamType> {
protected param!: AParamType;
}
Playground Link
I got this working function which I would like to have a better typedef for than any as action and return type. action should be a class and the return type should be class which extends action.
function createAction(state: string, action: any): any {
return class extends action {
public static readonly type = `[${state}] ${action.name}`;
constructor(...args: any) {
super(...args);
}
};
}
I tried using Angulars Type<T> which didn't work out.
If the only option to make this work would be to have an Interface on my action class and using that Interface for typedef it would be fine as well, as long as the Interface would be somewhat generic.
I imagine something along the lines of this as a solution, but i can't quite get it to work:
function createAction<T>(state: string, action: Type<T>): Type<X extends T> {
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.
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);
}
}