0

I'm creating a parent and child class in Typescript. Each one defines some fields (properties). I'm trying to keep the code clean, using the Partial<T> method for the constructors. But I'm not sure how to pass the parent's properties to the parent in the super() call and initialize the child's props with only the child's unique props in Object.assign() -- if that's even the right paradigm. Here's what I have:

export class Parent {
  name: string = ''
  durationInSec: number = 0.0
  optionalField?: string;
  constructor(init: Partial<Parent>) {
    Object.assign(this, init)
  }
}

export enum Types { A, B, C }

export class ChildA extends Parent {
  kind = Types.A
  childRequired1: string = '' // ??? have to set defaults for all props with this technique
  childOption1?: string
  constructor(init: Partial<ChildA>) {
    super(init) // ??? wrong, passes too many things to super
    Object.assign(this, init) // this would be fine alone, but tsc says need to call super()
  }
}

// similar for ChildB, ChildC etc.
let obj1 = new ChildA({name: 'foo', childRequired1: 'bar'})

Is this even a good paradigm in Typescript? Is there a better way?

2
  • "??? have to set defaults for all props with this technique" ... what's the alternative? If you have required props you need to set defaults somewhere, right? Or am I missing something? Also, why not call super({}) in the subclass and then do your Object.assign(this, init) afterward? Commented Sep 6, 2019 at 15:33
  • I'd like some fields of parent to be required and some optional, and same w/ child. Using this method it seems I have to set defaults even for the required fields because of Partial<T>, so they're not really required. Commented Sep 6, 2019 at 17:08

1 Answer 1

1

The problem is that init is a Partial<ChildA> so if you don't declare a default for childRequired1 then there's nothing preventing someone from doing new ChildA({}) in fact Parent.durationInSec is actually not optional on parent but you are relying on the default being there.

The second part of the problem is that it appears that using Object.assign(this, ...) in the constructor does not seem to satisfy the requirement of initialising all required parameters in the constructor. There seems to be an open issue about this.

The way I see it you have two options:

  1. Put defaults in all the required types to ensure they always have a value and Partial can still be used

  2. Be much more verbose:

export class Parent {
  name: string;
  durationInSec: number;
  optionalField?: string;
  constructor(init: Parent) {
      this.name = init.name;
      this.durationInSec = init.durationInSec;
      this.optionalField = init.optionalField;
  }
}

export enum Types { A, B, C }

export class ChildA extends Parent {
  kind: Types;
  childRequired1: string;
  childOption1?: string;
  constructor(init: ChildA) {
    super(init as Parent) // Downcasted, it should be ok.
    this.kind = init.kind;
    this.childRequired1 = init.childRequired1;
    this.childOption1 = init.childOption1;
  }
}

let obj1 = new ChildA({
    name: 'foo',
    childRequired1: 'bar',
    durationInSec: 1,
    kind: Types.A
});
Sign up to request clarification or add additional context in comments.

2 Comments

I was afraid of that. So much repetition! Have to type each field name three times. Maybe it's better to just go with constructor parameter properties? That seems not as cool because you don't get the nice named arguments (easier to extend, order doesn't matter, etc.). Also with those, you have to repeat all the base class's members in each derived class's super() call and constructor arg list, so it's not even that much better.
You can also do the "hacky" thing of using things like childRequired1!: string to make TypeScript think that it always will have a value. However you'd lose any sort of sanity check if you forgot to provide a value

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.