Typescript equals - javascript

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

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

Assigning child class to a parent class typed property

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

Cannot set property of Interface in TypeScript

I have create a simple interface
module Modules.Part
{
export interface IPart
{
PartId: number;
partNumber: string;
description: string;
}
}
then i have declare this interface in another interface
module Interfaces.Scopes {
export interface IPartScope extends ng.IScope {
part: Modules.Part.IPart;
vm: Controllers.PartCtrl;
}
}
i have use this interface in my class
module Controllers
{
export class PartCtrl
{
constructor(public scope:Interfaces.Scopes.IPartScope)
{
scope.part.PartId = 1;
scope.part.partNumber = "123part";
scope.part.description = "description";
}
}
}
when i am going to set property of IPart interface in my class it's give me following error
TypeError: Cannot set property 'PartId' of undefined
please let me know how to solve this
I'd say, you first need to initialize the part property of the PartCtrl.scope property (which you implicitly defining through the constructor):
constructor(public scope:Interfaces.Scopes.IPartScope)
{
scope.part = [initializer];
scope.part.PartId = 1;
scope.part.partNumber = "123part";
scope.part.description = "description";
}
Since the scope.part is of interface type IPart, you can't create an instance of it but rather have to resolve it somehow. For example:
export interface IActivator<T> {
new (): T;
}
export class PartCtrl<TPart extends IPart>
{
constructor(public scope:Interfaces.Scopes.IPartScope,
activator: IActivator<TPart>)
{
scope.part = new activator();
// ...
}
}
Now you can use the PartCtrl<TPart> in the following way:
export class ConcretePart implements IPart
{
public PartId: number;
public partNumber: string;
public description: string;
}
var ctrl = new PartCtrl(ConcretePart);
Hope this helps.
You can initialize the property "part" with an anonymous object.
scope.part =
{
PartId = 1;
partNumber = "123part";
description = "description";
}
Of course you can also define a class that implements IPart and instantiate it.

Categories

Resources