Subclass method signature inference - javascript

I have the following abstract class
// AbstractFiller.ts
export abstract class AbstractFiller {
public abstract fill(data: number | string | boolean | Date, numberOfFillers: number): string;
}
and several filler subclasses
export class WhiteSpacesFiller extends AbstractFiller {
public fill(data: number | string | boolean | Date, numberOfFillers: number): string {
// logic
}
}
export class ZerosFiller extends AbstractFiller {
public fill(data: number | string | boolean | Date, numberOfFillers: number): string {
// logic
}
}
// ...etc
Is there a way that TS would infer the method signature from the abstract class so that I have:
No type duplication in every subclass
Strict signature enforcing e.g. removing number from the type of data in a subclass will not throw an error.

Typescript will not infer method parameters from base class. The way it works is that after the class is typed, the class is checked for compatibility with the base class. This mean that a parameter in a derived class can be of a derived type (this is not sound but class method parameters relate bivariantly even under strict null checks).
One thing that can be done to reduce the amount of type duplication is to use Parameters with rest parameter destructuring.
export abstract class AbstractFiller {
public abstract fill(data: number | string | boolean | Date, numberOfFillers: number): string;
}
export class WhiteSpacesFiller extends AbstractFiller {
public fill(...[data, numberOfFillers]: Parameters<AbstractFiller['fill']>): string {
return ""
}
}

Excuse me if I misunderstand the question, but I think you can solve your problem with generics i.e. have your AbstractFiller class be generic.
That would look something like this:
export abstract class AbstractFiller<T> {
public abstract fill(data: T, numberOfFillers: number): string;
}
export class WhiteSpacesFiller extends AbstractFiller<string> {
public fill(data: string, numberOfFillers: number) {
//logic
return data;
}
}
export class ZerosFiller extends AbstractFiller<number> {
public fill(data: number, numberOfFillers: number) {
return data.toString();
}
}

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

Sequelize / Typescript: Types of parameters 'values' and 'values' are incompatible

I'm trying to create a BaseModel from which all my models are inherited.
I'm also trying to create a separate method() function that can get any of my models as argument.
This is my example code:
import {
Model, Optional
} from 'sequelize'; // v6.3.5
interface DefaultAttributes {
id: number;
}
interface TestAttributes extends DefaultAttributes {
field: number;
}
class BaseModel<T extends DefaultAttributes> extends Model<T, Optional<T, 'id'>> {
static test() {
}
}
class MyModel extends BaseModel<TestAttributes> {
}
function method<T extends typeof BaseModel>(model: T) {
model.test();
}
method(MyModel) // <<<<<< TypeScript ERROR
I've tried to do everything just as the documentation described. https://sequelize.org/master/manual/typescript.html
I'm getting the following TypeScript error:
TS2345: Argument of type 'typeof MyModel' is not assignable to parameter of type 'typeof BaseModel'.   Types of parameters 'values' and 'values' are incompatible.     Type 'Optional<T, "id">' is not assignable to type 'Optional<TestAttributes, "id">'.       Property 'field' is missing in type 'Optional<T, "id">' but required in type 'Pick<TestAttributes, "field">'.
Can pls someone help me what did I do wrong, or how can I create a method that can receive any UniqueModel inherited from BaseModel?
Try this:
import {
Model, Optional
} from 'sequelize'; // v6.3.5
interface DefaultAttributes {
id: number;
}
interface TestAttributes extends DefaultAttributes {
field: number;
}
class BaseModel<T extends DefaultAttributes> extends Model<T, Optional<T, 'id'>> {
static test() {
}
}
class MyModel extends BaseModel<TestAttributes> {
}
function method<T extends BaseModel>(model: T) {
model.test();
}
method(MyModel)

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?

Typescript abstract optional method

I have an abstract class with some abstract methods. Is there a way to mark some of these methods as optional?
abstract class Test {
protected abstract optionalMethod?(): JSX.Element[];
protected abstract requiredMethod(): string;
}
For some reasons I can add the ? as a suffix but it seems like it does nothing at all because I have to still implement the method in the derived class. For now I am using it like this to mark it that it can return null which is basically the poor mans optional.
protected abstract optionalMethod?(): JSX.Element[] | null;
You can do this with class and interface merging:
interface Foo {
print?(l: string): void;
}
abstract class Foo {
abstract baz(): void;
foo() {
this.print && this.print('foo');
}
}
class Bar extends Foo {
baz(): void {
if (this.print) {
this.print('bar');
}
}
}
Link to the above code in the Typescript playground
I'm not sure if this changed at some point, but today (TS 4.3) you can simply make the base-class optional method non abstract:
abstract class Base {
protected abstract required(): void;
protected optional?(): string;
print(): void {
console.log(this.optional?.() ?? "Base");
}
}
class Child1 extends Base {
protected required(): void { }
}
class Child2 extends Base {
protected required(): void { }
protected optional(): string {
return "Child";
}
}
const c1 = new Child1();
c1.print();
const c2 = new Child2();
c2.print();
Try it on the TS Playground.
The concept of abstract is something which is not defined but will be in the inherited classes. That is why we can't have abstract methods without implementation.
I suggest you to create non-abstract method already implemented in your base class to achieve your goal:
abstract class A {
protected abstract requiredMethod(): string;
protected emptyDefinition(): string | void {};
protected optionalMethod(): string {
return "something optional";
};
}
class B extends A {
protected requiredMethod(): string {
return "something required";
}
}
Typescript doesn't support the "omission" of optional abstract functions, but you can explicitly leave it undefined as below:
abstract class Test {
protected abstract optionalMethod?(): JSX.Element[];
protected abstract requiredMethod(): string;
}
class MyTest extends Test {
protected optionalMethod: undefined;
protected requiredMethod(): string {
return 'requiredResult';
}
}
You don't need interface merging.
You only have to work with overload methods without default implementations like so:
abstract class Test {
...
private someMethod() {
...
this.optinalMethod?.();
}
...
protected abstract requiredMethod(): string;
protected optionalMethod?(): string;
}
abstract indicated that it has to be overwritten in the implementing class.
in case of an optional method we however don't want to enforce overriding it.
This has been around for a while but and answered correctly above, but the example isn't clear. You can use interface merging like this too.
interface Test {
optionalMethod?(): JSX.Element[];
}
abstract class Test {
protected abstract requiredMethod(): string;
}

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

Categories

Resources