Assigning child class to a parent class typed property - javascript

I do have the following 2 base clases:
class BaseModel {}
class BaseService{
protected model:BaseModel;
}
Now I want to implement BaseHelper and BaseService for a specific use case and assign a derived class to my property.
class MyModel extends BaseModel{
constructor(param:string){
super();
}
}
class MyService extends BaseService {
model = MyModel;
}
However, this gives me the error Type 'typeof MyModel' is not assignable to type 'BaseModel'.
Important: I want to attach the class MyModel, not an instance of the class MyModel!

You need to instantiate MyModel using the new keyword (new MyModel()).
You assigned the actual class (model = MyModel) instead of an instance of it.
Also, you might want to make BaseService generic:
class BaseModel {}
class BaseService<T extends BaseModel> {
protected model: T;
constructor(model: T) {
this.model = model;
}
}
class MyModel extends BaseModel{}
class MyService extends BaseService<MyModel> {
constructor() {
super(new MyModel());
}
}
(code in playground)
Edit
If you need the class and not the instance, then something like:
class BaseModel {}
type BaseModelConstructor = { new(): BaseModel };
class BaseService {
protected modelCtor: BaseModelConstructor;
}
class MyModel extends BaseModel {}
class MyService extends BaseService {
modelCtor = MyModel;
}
(code in playground)
Or you can use generics here as well:
class BaseModel {}
type BaseModelConstructor<T extends BaseModel> = { new(): T };
class BaseService<T extends BaseModel> {
protected modelCtor: T;
}
class MyModel extends BaseModel {}
class MyService extends BaseService<BaseModel> {
modelCtor = MyModel;
}
(code in playground)
If your derived classes have different ctor signatures then you can either deal with it in the base ctor type:
type BaseModelConstructor<T extends BaseModel> = { new(...args: any[]): T };
Here you can pass any count and kind of parameters, but you can also supply different signatures:
type BaseModelConstructor<T extends BaseModel> = {
new(): T;
new(str: string): T;
new(num: number, bool: boolean): T;
};
But you can also use a different type per derived class:
type MyModelConstructor = { new(param: string): MyModel };

model should be an instance of MyModel:
class MyModel extends BaseModel{}
class MyService extends BaseService {
model = new MyModel()
}

Use instance of BaseModel
class MyService extends BaseService {
model = new MyModel();
}

Related

Why I don't get compile time error when I am not implementing some method from my interface which is implemented in base class?

I have the following code
interface IDownload {
downloadFile(): void;
}
class BaseClass implements IDownload {
downloadFile(): void {
console.log('some logic here');
}
}
class Sub extends BaseClass {
}
so my sub class has access to the methods from BaseClass because we are extending from that class.
My base class implement some method from interface.
When i try to extend the class and implement again the same interface
class Sub extends BaseClass implements IDownload {
}
I don't get compile time error that i need to implement the method from IDownload. I guees it is like that because it sees that the base class already implements it.
But i want to have that check also here in my sub class because i want to have the interface contract in the base class where all the methhods will exist on the sub class.
How can i do this ?
If you want all the implementation in the sub class, why don't you just implement the interface in the sub class?
interface IDownload {
downloadFile(): void;
}
class BaseClass {
}
class Sub extends BaseClass implements IDownload {
downloadFile(): void {
console.log('some logic here');
}
}

Typescript optional parameters overload

I have a class e.g.
class SomeClass {
someMethod(param1: string; param2?: string) { ... }
}
And another one that extends from fore
class AnotherClass extends SomeClass {
someMethod(param1: string) { ... }
}
Also I've got a third class that uses instance of one of these classes as generic e.g.
class ThirdClass<T extends SomeClass> {
instanceClass: T;
}
So I'm getting an error that someMethod in AnotherClass is not compatible with SomeClass, to solve the problem I should make an overload for someMethod in SomeClass :
someMethod(param1: string); // <-- overload
someMethod(param1: string; param2?: string) { ... }
Is there any other way to say TS that everything is ok?

Singleton decorator in typescript

I'm trying to use this code:
function singleton<T extends { new() }>(constructor: T): T {
return new constructor()
}
#singleton
export default class SomeClass {
constructor() {}
public method(): string {
return 'Hello!'
}
}
console.log(SomeClass.method())
And it works. The message "Hello!" is displayed in the console. But the typescript compiler says there's error:
What's wrong?
Unfortunately the compiler cannot know what changes the decorator does to the class. You could try something like this:
function singleton<T>(constructor: new ()=> T): T {
return new constructor()
}
export const SomeClass = singleton(class {
constructor() {}
public method(): string {
return 'Hello!'
}
});
console.log(SomeClass.method())
Just use new before your class declaration:
export default new class {
constructor() {}
public method(): string {
return 'Hello!'
}
});

Typescript equals

How do I implement equals in Typescript?
I've tried a few methods, both didn't work.
Option 1:
abstract class GTreeObject<T>{
abstract equals(obj: T): boolean;
}
class GNode extends GTreeObject<GNode>{
public equals(obj: GNode){
//Implement
}
}
Results in: Generic type 'GNode' requires 1 type argument(s).
Option 2 would involve casting during runtime, but how to do the casting?:
abstract class GTreeObject{
abstract equals(obj: Object): boolean;
}
class GNode extends GTreeObject{
//How to cast Object to GNode??
public equals(obj: Object){
//Implement
}
}
How to solve reliably?
According to the TypeScript playground, the only problem is that you forgot to declare the return type of your implementation of equals in GNode:
abstract class GTreeObject<T>{
abstract equals(obj: T): boolean;
}
class GNode extends GTreeObject<GNode>{
public equals(obj: GNode) : boolean {
// ----------------------^^^^^^^^^^
return /*implementation*/true;
}
}
This complete example works, for instance:
abstract class GTreeObject<T>{
abstract equals(obj: T): boolean;
}
class GNode extends GTreeObject<GNode>{
private value: number;
public constructor(value: number) {
super();
this.value = value;
}
public equals(obj: GNode) : boolean {
return this.value === obj.value;
}
}
let g1 : GNode = new GNode(42);
let g2 : GNode = new GNode(42);
let g3 : GNode = new GNode(27);
console.log(g1.equals(g2)); // true
console.log(g1.equals(g3)); // false
Re your comment:
Sorry for the hassle. This seems to work for GNode, but when implementing GTree:
class GTree<T> extends GTreeObject<GTree>
this seems to result in an error: Generic type 'GTree' requires 1 type argument(s). Is there a problem using two generic types in the class definition (taking + passing a generic)?
You'd need to declare the type parameter to GTreeObject as GTree<T>, not just GTree:
class GTree<T> extends GTreeObject<GTree<T>> {
// -------------------------------------^^^
public equals(obj: GTree<T>) : boolean {
// ---------------------^^^
return /*implementation*/true;
}
}

Multiple Class Inheritance In TypeScript

What are ways to get around the problem of only being allowed to extend at most one other class.
class Bar {
doBarThings() {
//...
}
}
class Bazz {
doBazzThings() {
//...
}
}
class Foo extends Bar, Bazz {
doBarThings() {
super.doBarThings();
//...
}
}
This is currently not possible, TypeScript will give an error. One can overcome this problem in other languages by using interfaces but solving the problem with those is not possible in TypeScript.
Suggestions are welcome!
This is my workaround on extending multiple classes. It allows for some pretty sweet type-safety. I have yet to find any major downsides to this approach, works just as I would want multiple inheritance to do.
First declare interfaces that you want to implement on your target class:
interface IBar {
doBarThings(): void;
}
interface IBazz {
doBazzThings(): void;
}
class Foo implements IBar, IBazz {}
Now we have to add the implementation to the Foo class. We can use class mixins that also implements these interfaces:
class Base {}
type Constructor<I = Base> = new (...args: any[]) => I;
function Bar<T extends Constructor>(constructor: T = Base as any) {
return class extends constructor implements IBar {
public doBarThings() {
console.log("Do bar!");
}
};
}
function Bazz<T extends Constructor>(constructor: T = Base as any) {
return class extends constructor implements IBazz {
public doBazzThings() {
console.log("Do bazz!");
}
};
}
Extend the Foo class with the class mixins:
class Foo extends Bar(Bazz()) implements IBar, IBazz {
public doBarThings() {
super.doBarThings();
console.log("Override mixin");
}
}
const foo = new Foo();
foo.doBazzThings(); // Do bazz!
foo.doBarThings(); // Do bar! // Override mixin
This is possible with interfaces:
interface IBar {
doBarThings();
}
interface IBazz {
doBazzThings();
}
class Foo implements IBar, IBazz {
doBarThings() {}
doBazzThings(){}
}
But if you want implementation for this in a super/base way, then you'll have to do something different, like this:
class FooBase implements IBar, IBazz{
doBarThings() {}
doBazzThings(){}
}
class Foo extends FooBase {
doFooThings(){
super.doBarThings();
super.doBazzThings();
}
}
Not really a solution to your problem, but it is worth to consider to use composition over inheritance anyway.
Prefer composition over inheritance?

Categories

Resources