Typescript automatically get interface properties in a class

Viewed 12880

Hello TypeScript experts.

I have the following code but I have to repeat the interface properties in the class otherwise I get:

Class incorrectly implements interface

When using an interface, is there a TypeScript shorthand for doing this without having to declare Id: number; and all the other properties in the class? Thx

interface INavigation {
  Id: number;
  AppId: number;
  NavId: number;
  Name: string;
  ParentId: string;
  PageURL: string;
  Position: string;
  Active: string;
  Desktop: string;
  Tablet: string;
  Phone: string;
  RoleId: string;
  Target: string;
}

class Navigation implements INavigation {

  Id: number;
  AppId: number;
  NavId: number;
  Name: string;
  ParentId: string;
  PageURL: string;
  Position: string;
  Active: string;
  Desktop: string;
  Tablet: string;
  Phone: string;
  RoleId: string;
  Target: string;

  constructor(navigation: any) {
    this.Id = navigation.Id
    this.AppId = navigation.NavAppId
    this.NavId = navigation.NavId
    this.Name = navigation.NavName
    this.ParentId = navigation.NavParentId
    this.PageURL = navigation.NavPageURL
    this.Position = navigation.NavPosition
    this.Active = navigation.NavActive
    this.Desktop = navigation.NavDesktop
    this.Tablet = navigation.NavTablet
    this.Phone = navigation.NavPhone
    this.RoleId = navigation.NavRoleId
    this.Target = navigation.NavTarget
  }
}
5 Answers

There is no built-in support for this.

We can however use a function that returns a class as the base type of our class. This function can lie a little bit and claim it implements the interface. We could also pass in some defaults for the members if necessary.

interface INavigation {
  Id: number;
  AppId: number;
  NavId: number;
  Name: string;
  ParentId: string;
  PageURL: string;
  Position: string;
  Active: string;
  Desktop: string;
  Tablet: string;
  Phone: string;
  RoleId: string;
  Target: string;
}

function autoImplement<T>(defaults?: Partial<T>) {
  return class {
    constructor() {
      Object.assign(this, defaults || {});
    }
  } as new () => T
}

class Navigation extends autoImplement<INavigation>() {
  constructor(navigation: any) {
    super();
    // other init here
  }
}

If we want to have a base class, things get a bit more complicated since we have to perform a bit of surgery on the base type:

function autoImplementWithBase<TBase extends new (...args: any[]) => any>(base: TBase) {
  return function <T>(defaults?: Partial<T>): Pick<TBase, keyof TBase> & {
    new(...a: (TBase extends new (...o: infer A) => unknown ? A : [])): InstanceType<TBase> & T
  } {
    return class extends base {
      constructor(...a: any[]) {
        super(...a);
        Object.assign(this, defaults || {});
      }
    } as any
  }
}

class BaseClass {
  m() { }
  foo: string
  static staticM() { }
  static staticFoo: string
}

class Navigation extends autoImplementWithBase(BaseClass)<INavigation>() {
  constructor(navigation: any) {
    super();
    // Other init here
  }
}

Navigation.staticFoo
Navigation.staticM
new Navigation(null).m();
new Navigation(null).foo;

A mix between the 2 answers, for avoid long assignment in the constructor by using class/interface merging and Object.assign in the constructor :

interface Foo {
    a: number;
    b: number;
}

interface Baz extends Foo { }

class Baz  {
    c: number = 4

  constructor (foo: Foo) {
    Object.assign(this, foo, {})
  }
  
  getC() {
    return this.c
  }
}

let foo: Foo = {
    a: 3,
    b: 8
}

let baz = new Baz(foo)
// keep methods and properties
console.warn(baz)

in case you using angular you can pass whole component as prop which is crazy

Child Component TS:

@Input() parent!: YourParentClass;

Parent Component HTML:

<child [parent]="this" ></child>

Class declarations should explicitly implement interfaces.

Related