Get Off My Lawn
Get Off My Lawn

Reputation: 36311

TypeScript interface optional parameter but not viewed as optional

I have an interface that has a property that is optional, but it is set within my constructor with default values and then overridden with values passed in from the first parameter of the constructor. If the properties are not set the it uses the defaults.

How can I get it so that the parameter is still optional when passed in to the constructor, but when used within the class it is seen as set. I don't want to get the following error:

Object is possibly 'undefined'.

TypeScript Playground

export interface IMain {
  req: string;
  prop?: ISecondary;
}

export interface ISecondary {
  a?: string;
  b?: string;
}
export class ABC {
  public main: Main;

  public constructor(main: Main) {
    this.main.prop = {
      a: "A",
      b: "B",
      ...main.prop
    };
  }

  public doSomething() {
    this.main.prop.a = "Z";
  }
}

new ABC({
  req: 'cat'
});

Upvotes: 0

Views: 1434

Answers (2)

brunnerh
brunnerh

Reputation: 184516

You can mark the property as required for the class and change the constructor to this:

export class ABC {
    public main: Required<IMain>;

    public constructor(main: IMain) {
        this.main = {
            ...main,
            prop: {
                a: "A",
                b: "B",
                ...main.prop
        }};
    }
}

If you have properties that should still be optional you have to be a bit more specific with your types. e.g.

type OptionalProps<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

export interface IMain {
    req: string;
    prop: ISecondary; // Required here
}

export class ABC {
    public main: IMain;

    public constructor(main: OptionalProps<IMain, 'prop'>) {
        this.main = {
            ...main,
            prop: {
                a: "A",
                b: "B",
                ...main.prop
        }};
    }
}

Sometimes you might just want to define separate interfaces if this gets too complicated. You can always extract the identical properties to a parent interface. e.g.

interface IMainRequired {
    req: string;
}
interface IMainOptional {
    prop: ISecondary;
}
export type IMain = IMainRequired & IMainOptional
export type IMainArg = IMainRequired & Partial<IMainOptional>

export class ABC {
    public main: IMain;

    public constructor(main: IMainArg) {
        this.main = {
            ...main,
            prop: {
                a: "A",
                b: "B",
                ...main.prop
        }};
    }
}

Upvotes: 1

AKX
AKX

Reputation: 168967

One approach would be to make the property non-optional, and then use a Partial<IMain> to make the argument's all properties optional:

export interface IMain {
  prop: ISecondary;
}

export interface ISecondary {
  a?: string;
  b?: string;
}

const ISecondaryDefaults: ISecondary = {
  a: "A",
  b: "B",
};

export class ABC {
  public main: IMain;

  public constructor(main: Partial<IMain>) {
    this.main = {
      ...main,
      prop: {
        ...ISecondaryDefaults,
        ...main.prop,
      },
    };
  }

  public doSomething() {
    this.main.prop.a = "Z";
  }
}

Upvotes: 1

Related Questions