Typescript abstract optional method - javascript

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

Related

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?

Subclass method signature inference

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

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?

(MyClass) JavaScriptObject.createObject() vs. JavaScriptObject.createObject().cast()

Are the following JSNI wrapper classes doing the same?
public final MyClass extends JavaScriptObject {
// ...
private static MyClass createImpl() {
return (MyClass) JavaScriptObject.createObject();
}
}
and
public final MyClass extends JavaScriptObject {
// ...
private static MyClass createImpl() {
return JavaScriptObject.createObject().cast();
}
}
The source of JavaScriptObject.cast() looks like this:
public final <T extends JavaScriptObject> T cast() {
return (T) this;
}
So the answer is "yes", it is doing the same.
See:
http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/src/com/google/gwt/core/client/JavaScriptObject.java?r=5303#116

Categories

Resources