#Overriding an inherited property's type with generics.

36 messages · Page 1 of 1 (latest)

slow atlas
#

I have a class that looks like this:

export class Block {
  protected structure: Structure;

  public constructor() {
    this.structure = {
      ...
    };
  }
}

Now I need to extend Block where I override the "structure" type to something more compatible for that structure, essentially, just adds extra properties that are irrelevant to Block but may be needed by the extending class, so I have tweaked it so that it is:

export class Block<StructureType extends IBlock = IBlock> {
  protected structure: StructureType;

  public constructor() {
    this.structure = {
      ...
    };
  }
}

This works correctly, and allows me to extend it like so:

import { Block } from '../some/loc';

export class Field extends Block<IField> {
  public constructor() {
    super();
    this.structure = { ... };
  }
}

The issue I am seeing is that, in the "Block" class, when I do this.structure = { ... } I am seeing the attached screenshot.

It's worth noting the IField type extends IBlock by:

export type IField = IBlock & { ... }

I'd appreciate any thoughts or pointers of stupid stuff I am doing.

#

I have also tried adding the "default" required properties within the structure (i.e. IBlock type), but I get the same error, I am unsure what I am missing here.

#

Overriding an inherited property's type with generics.

dull quest
#

Block class doesn't know what StructureType is, so it's not correct to assign something to it unless you know that it is indeed the same type.

slow atlas
#

I thought by saying that X extends IBlock = IBlock, I am telling it, that at the least it will resemble IBlock?

dull quest
#

No, StructureType can be a more specific kind of IBlock.

#

If it must be IBlock then that would be pointless.

slow atlas
#

Hmm okay, so if I was to type the "{ ... }" part to be of type IBlock that would solve the issue?

dull quest
#

Easiest solution is to lift the assignment up:

class Block<StructureType extends IBlock = IBlock> {
  protected structure: StructureType;

  public constructor(structure: StructureType) {
    this.structure = structure
  }
}
class Field extends Block<IField> {
  public constructor() {
    super({ ... })
  }
}
#

It's not possible for Block itself to know what StructureType is, but it is possible for the class extending it to know.

slow atlas
#

Ahh okay, hmm, the thing is block technically will never be initialised on it's own, the idea was it would have it's own "base data" and then other classes will extend that with additional data, i.e.:

BaseField

InputField extends BaseField

But in this example BaseBlock would technically have data and methods against it, like setters and getters on "default properties", etc.

#

Essentially, BaseBlock should have a very "basic" set of default data to start with, each extending field just adds onto that.

Would it be better for me, to make some kind of abstract before the base block that deals with what you suggested here?

dull quest
#

Then make Block and structure abstract, so the responsibility will be left to the child classes.

slow atlas
#

Sure, but each extending class would take the previous data and merge it's own defaults to it? would marking them as abstract force the direct extending class to actually implement the base properties as well? (trying to avoid redundant data essentially).

#

I think maybe I am approaching this whole thing wrong possibly.

Essentially:

I have a class called BaseBlock this is my base element, this will contain functions, helpers and a structure that is the base for everything that extends it.

Then I can extend that class with another class, that may have it's own functions, helpers, and will take the parent structure, and merge it with it's own defaults, creating a structure that then represents that field at the time.

So the idea would be from an inheritance standpoint:

Block -> Field -> Input
or
Block -> Collection -> Table -> Row -> Cell

Just examples, etc.

#

The idea is that each class before it will contain required properties for all extending classes.

#

It kind of means you would have:

export class Field extends BaseBlock<FieldType> {}

export class Input extends Field<InputType> {}

etc.

dull quest
#

What information that you are storing which requires it must be in structure?

#

Why not each child class stores its own data in its own properties?

slow atlas
# dull quest Why not each child class stores its own data in its own properties?

It's essentially a page structure.

So the base block may contain a settings object that has visible: true for example, I know that every "element" or "component" on the frontend that the JSON will be sent to will need this property.

Then say we have a Field class that adds, validators, because Field will be used for HTML form elements, so that may contain additional data on top of the defaults from Block.

Then we may create Input that extends Field, this will contain the standard validators, but I may need to define the element's type attribute, this will be another property that goes into structure for example, and so on.

The idea is that structure at the last extending class can simply say:

"Give me the representation of Input which will contain, Block properties, merged with field's properties, also merged with input's properties.

#

Here, I was re-wording it to make more sense

#

For context, previous:
https://discord.com/channels/508357248330760243/1336702186444292249
I have made a new post, because the previous did not make sense anymore.

So the issue I am facing is as follows:

I am working on a simple "page structure" object that defines the layout of a page, this is a prototype idea that I am toying with, the part I need to figure out is how to deal with typing, imagine I have this class:

export class Base {
  protected structure: IBase;

  public constructor() {
    this.structure = { ... };
  }
}

As you can see I have created a base class, where the structure contains some very generic properties that EVERY extending class would also require.

Now in this example, I need another class that takes base, and will add some additional properties:

export class Field extends Base {

  public constructor() {
    super();
    this.structure.validators = [];
  }

  public addValidator(validator) {
    this.structure.validators.push(validator);
  }
}

The issue I see here, is that at the moment, IBase does NOT contain the validators property, therefore, it will error when I try and add it, and also addValidator will fail because again, validators is unknown to the structure.

Additionally I will also need to extend Field for example for an input, so I might do:

export class Input extends Field {
  public constructor() {
    super();
    this.structure.type = 'text';
  }

  public setType(inputType) {
    this.structure.type = inputType;
  }
}
dull quest
#

So the reason all data must be in this one single structure property (insted of being spread out) is so that it's easier for you to send everything at once?

slow atlas
# dull quest So the reason all data must be in this one single `structure` property (insted o...

Pretty much yeah, because the API is going to be JSON data, so I can simply at the end go:

input.render();

And render will return a processed array of data ready for me to use.

I have an alternative way, where each property is stored as a property against the class, and the render function simply collects the related properties, puts them into the array and then returns that, and it goes along, but I thought this could be simpler, which feels like the right way to do this.

so each render function would look like:

render() {
  const struct = super.render();
  struct.type = this.type;
  return struct;
}
#

I was just trying to prototype maybe a lazier way of doing it.

dull quest
#

Well I won't comment on your design, but if that's what you are going for then one approach is something like:

class Base<T> {
    protected data: { baseProperty: string } & T

    constructor(data: T) {
        this.data = {
            baseProperty: 'hello world',
            ...data,
        }
    }
}

class Child extends Base<{ childProperty: number }> {
    constructor() {
        super({
            childProperty: 42,
        })
    }
}
#

You can extend the same pattern further.

slow atlas
#

because in your example it only works at one level, but this one class may have inherited many classes further up the chain.

#

I may just switch up my design, so it's a little more "cleaner" as this feels wrong what I am trying to do and causing lots of complexity maybe.

Thanks for the suggestions though, appreciate it.

dull quest
#

I mean, you can extend the same pattern with class Child<T>

slow atlas
#

In what sense sorry? like:

class Child<T> extends Base<T & ChildProps> {}

?

dull quest
#

Yes.

#

But personally I wouldn't have all information stored in one property, and would instead do something like:

class Base {
    private baseProperty: string

    render() {
        return {
            baseProperty: this.baseProperty,
        }
    }
}

class Child extends Base {
    private childProperty: number

    render() {
        return {
            ...super.render(),
            childProperty: this.childProperty,
        }
    }
}

(Well, personally I don't like using classes at all, but that's a separate topic)

slow atlas
#

Yeah, that's where I wonder if that wouldn't be nice to deal with, I guess the alternative option (using the render function to "merge everything together") has problems, in the sense that, if I need to "see" other properties, I would need a getter on everything so that lower in the chain, it can access it, not really a bad thing, and probably good practice, but something I'd need to be aware of, I might try both ideas and see what is preferred.

dull quest
#

I mean you can make the properties protected so the child class can access them directly. Each class should do its own render implementation because only the class itself knows how to properly render.