Typescript inheritance extend properties

Viewed 1018

Well I have a simple inheritance of two Objects user and participant.

The user is defined as


export type userDoc = {
  firstname: string
  lastname: string
  email: string
}

export class User {
  props: userDoc = {
      firstname: "",
      lastname: "",
      email: "", 
      {...}
  }
}

And the participant (inheriting from user) as

type participantDoc = {
    pseudonym: string
    karma: number
    impact: number
    level: number
    experience: number
  }

export class Participant extends User {
    props: participantDoc = {
      pseudonym: "",
      karma: 0,
      impact: 0,
      level: 0,
      experience: 0
    }
}

Now what I am hoping to achieve is to extend the attributes of the parent class in the child class. I am working with props because of mongodb and because its safer to assign it this way instead of Object.assign().

The error typescript is throwing:

TS2416: Property 'props' in type 'Participant' is not assignable to thesame property in base type 'User'.
  Type 'participantDoc' is missing the following properties from type 'userDoc': firstname, lastname, email, and 6 more.
2 Answers

You will have to extend the types of props just like you did for class.

Change

export type participantDoc = {
    pseudonym: string
    karma: number
    impact: number
    level: number
    experience: number
}

to

export type participantDoc = {
    pseudonym: string
    karma: number
    impact: number
    level: number
    experience: number
} & userDoc

Also, if you are willing to declare your types as interfaces, you could do this:

export interface userDoc {
  firstname: string
  lastname: string
  email: string
}

export interface participantDoc extends userDoc {
    pseudonym: string
    karma: number
    impact: number
    level: number
    experience: number
}

Inheritance can work between User and Participant classes if User is generic to specify the type of its props field.

Another tip is to rely on constants (the props values when empty) to get the type. It makes the code terser.

const emptyUserProps = {
    firstname: "",
    lastname: "",
    email: "", 
};

export type UserProps = typeof emptyUserProps;

export class User<TProps extends UserProps = UserProps> {
    props = emptyUserProps as TProps; // Type assertion `as TProps` is needed here in order to initialise the field using `emptyUserProps` (of type `UserProps`, not `TProps`)
}

// -------

const emptyParticipantProps = {
    pseudonym: "",
    karma: 0,
    impact: 0,
    level: 0,
    experience: 0,
};

export type ParticipantProps = typeof emptyParticipantProps;

export class Participant extends User<UserProps & ParticipantProps> {
    props = {
        ...emptyUserProps,
        ...emptyParticipantProps,
    };
}

But if User and Participant share only some data in props field, it's safer to achieve polymorphism using on a base interface rather than class inheritance:

export interface WithProps<TProps> {
    props: TProps;
}

// ----

const emptyUserProps = {
    firstname: "",
    lastname: "",
    email: "", 
};

export type UserProps = typeof emptyUserProps;

export class User implements WithProps<UserProps> {
    props = emptyUserProps;
}

// -------

const emptyParticipantProps = {
    pseudonym: "",
    karma: 0,
    impact: 0,
    level: 0,
    experience: 0,
};

export type ParticipantProps = typeof emptyParticipantProps;

export class Participant implements WithProps<UserProps & ParticipantProps> {
    props = {
        ...emptyUserProps,
        ...emptyParticipantProps,
    };
}

Another option is to use a single generic class with several static factory methods, one to create a "basic" user, another one for a participant:

const emptyUserProps = {
    firstname: "",
    lastname: "",
    email: "", 
};

const emptyParticipantProps = {
    pseudonym: "",
    karma: 0,
    impact: 0,
    level: 0,
    experience: 0,
};

export class User<TProps> {
    // `TProps` inferred to `typeof emptyUserProps`
    static createUser(props = emptyUserProps) {
        return new User(props);
    }

    // `TProps` inferred to `(typeof emptyUserProps) & (typeof emptyParticipantProps)`
    static createParticipant(props = {
        ...emptyUserProps,
        ...emptyParticipantProps,
    }) {
        return new User(props);
    }

    private constructor(public props: TProps) {}
}
Related