46

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
  }
}
1
  • 1
    While the answer below is superb, it might no be necessary to implement an interface at all if you don't have other classes implementing same interface Commented Nov 4, 2018 at 10:18

7 Answers 7

76

This is now possible in Typescript using class/interface merging.

interface Foo {
    a: number;
}

interface Baz extends Foo { }
class Baz {
    constructor() {
        console.log(this.a); // no error here
    }
}

https://github.com/Microsoft/TypeScript/issues/340#issuecomment-184964440

Sign up to request clarification or add additional context in comments.

4 Comments

thanks for the tip. this should be the chosen answer
This does add all the properties of the interface to the class, but how to avoid the huge mess of assignment in the constructor?
not as useful as I'd hoped given the immediately above comment - but better than a kick up the pants +1
^ To the above: use a single parameter (we'll call it 'propsObject') and give it the interface that you're merging into the class, then Object.assign(this, propsObject). You'll have to pass in an object when creating an instance of the class, but it will have all of the parameters of the interface nicely typed out for you.
17

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;

1 Comment

const n = new Navigation({Id: 'test'}); console.log(n.Id); // undefined Works when you add Object.assign(this, navigation, {}); to class Navigation constructor
5

Short answer, pure TypeScript

type InterfaceOf<ClassType> = {
    [Member in keyof ClassType]: ClassType[Member];
};

Example of use:

class MyClass {
  private num: number; // private member will be ignored by 'InterfaceOf'
  public bool: boolean;
  public foo() {};
}

type MyClassType = InterfaceOf<MyClass>;

class MyClass2 implements MyClassType { // Error from this line, missing foo
    public bool: boolean;
}

The error will say:
Class 'MyClass2' incorrectly implements interface InterfaceOf<MyClass>. Type 'MyClass2' is missing the following properties from type InterfaceOf<MyClass>: foo

Comments

2

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)

Comments

2

I was messing around trying to do this and found the following solution by using the classes own type for the constructor parameter:

export class Test {
    prop1: string
    prop2: number

    constructor (data: Test) {
        this.prop1 = data.prop1
        this.prop2 = data.prop2
    }
}

If you needed just the properties of the class (to ignore methods) you'd have to create a new type using Pick

type TestI = Pick<Test, 'prop1' | 'prop2'>

Comments

0

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>

Comments

-2

Class declarations should explicitly implement interfaces.

4 Comments

don't know what that means, can you provide an example please or elaborate - thx
it means you should implement all members which are in the interface
explicitly you wanted to say?
I'm sorry, I made mistake, yes explicitly

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.