25

I tried to use an Class as Interface for another class. With this I am trying to accomplish an up-to-date mockClass for Testing.

This should be possible and is an favorable approach, as state in https://angular.io/guide/styleguide#interfaces

And is demonstrate here: Export class as interface in Angular2

But strangely I'm getting an Error using Typescript 2.5.3 in VSCode 1.17.2

Error in class SpecialTest

[ts]
Class 'SpecialTest' incorrectly implements interface 'Test'.
  Types have separate declarations of a private property 'name'.

Samplecode:

class Test {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

class SpecialTest implements Test {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

What am I missing?

EDIT: use string instead of String , as @fenton suggested

2
  • 1
    Reading the error, if SpecialTest implements Test, does it need to declare private property name? Commented Nov 4, 2017 at 18:40
  • Yes. Without it would fail because it does not implement the interface. we use a class AS an interface... Commented Dec 15, 2020 at 10:20

5 Answers 5

33

Before we get started, when you extend a class, you use the extends keyword. You extend a class, and implement an interface. There are additional notes on this further down the post.

class SpecialTest extends Test {

Also, watch out for string vs String as this will trip you up. Your type annotations should almost certainly be string (lowercase).

And finally, you don't need to manually assign constructor parameters, so the original:

class Test {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    // ...
}

Is better expressed as:

class Test {
    constructor(private name: string) {
    }

    // ...
}

Now you can choose from a number of solutions to your problem.

Protected Member

Make the name member protected, then you can use it within sub classes:

class Test {
    protected name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

class SpecialTest extends Test {
    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

Interface

This is the solution that I think best matches your needs.

If you pull the public members into an interface, you should be able to treat both classes as that interface (whether or not you explicitly use the implements keyword - TypeScript is structurally typed).

interface SomeTest {
  getName(): string;
  setName(name: string): void;
}

You could explicitly implement it if you wish:

class SpecialTest implements SomeTest {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

Your code can now depend on the interface rather than on a concrete class.

Using a Class as an Interface

It is technically possible to reference a class as an interface, but there are problems ahead of you do this with implements MyClass.

Firstly, you are adding unnecessary complexity for anyone who has to read your code later, including the future you. You are also using a pattern that means you need to be careful about the keyword. Accidental use of extends may cause a tricky bug in the future when the inherited class is changed. Maintainers will need to become hawkeye over which keyword is used. And all for what? To preserve nominal habits in a structural language.

Interfaces are abstract, and less likely to change. Classes are more concrete, and more likely to change. Using a class as an interface ruins the entire concept of depending on stable abstractions... and instead causes you to depend on unstable concrete classes.

Consider the proliferation of "classes as interfaces" throughout a program. A change to a class (let's say we add a method) can inadvertently cause a change to ripple for vast distances... how many parts of the program now reject input, because the input doesn't contain a method that is not even used?

The better alternatives (when there are no access modifier compatibility issues)...

Create an interface out of the class:

interface MyInterface extends MyClass {
}

Or, just don't reference the original class at all in your second class. Allow the structural type system to check compatibility.

Side note... depending on your TSLint config, weak types (such as an interface with only optional types) will trigger the no-empty-interface-Rule.

Specific Case of Private Members

None of these (using a class as an interface, generating an interface from a class, or structural type) solve the problem of the private member. This is why the solution that solves the real problem is to create an interface with the public members on.

In the specific case of private members, such as in the question, let's think about what will happen if we continue with the original pattern? The natural outcome will be that to preserve the pattern of using the class as an interface, the visibility of members will be changed, like this:

class Test {
    public name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

And now we are breaking the more established principles of object-orientation.

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

4 Comments

Well typescript and angular are a bit....weird about their guidelines. Anyhow OP wants to follow angular / typescript guidelines by using a normal class as an interface, not the the actual interface structure TS provides (and therefore he doesn't want to extend sth. either).
Thanks @Fenton! The reason for implementign this way: I want to regularly use the Test-Class but implement a Mock-Up for Testing.so when changing the original Class the whole Mock should throw an error, because it needs to reflect the changes.
I've tested all your suggestion! They work fine :) Notice: an interface declaration must not be empty [tslint] no-empty-interface. So one could add something trivial.
@jvoigt - that's a concious choice that you put into your TSLint config - TypeScript allows these, they are called weak types. They are common in libraries such as jQuery where the options objects are merged with defaults, allowing you to specify any of the members that you like, or none.
27

To create an interface from a class, capturing only the public properties and not the implementation specifics, you can use Pick<Type, Keys> combined with the keyof operator.

class Test {
  foo: any;
  private bar: any;
}

class SepcialTest implements Pick<Test, keyof Test> {
  foo: any;
}

Pick<Type, Keys> Constructs a type by picking the set of properties Keys from Type.

[...] keyof T, the index type query operator. For any type T, keyof T is the union of known, public property names of T.

In the given example Pick<Test, keyof Test> resolves to Pick<Test, 'foo'> which is { foo: any }.

2 Comments

WOW. This might come in handy.
This is perfect. I have an Api class that I am mocking with MockApi. I wanted to avoid an extra base interface since it is purely for testing. Well done! This should be the accepted answer.
3

In this example parent class is supposed to be abstract and acts as an interface. TypeScript interfaces don't have access modifiers, all interface properties are expected to be public.

When concrete class like Test is used as interface a problem appears, because the implementation cannot implement private properties and cannot override them either.

Both Test and SpecialTest should either implement some abstract class as interface (in this case abstract class can have only public members) or inherit from it (in this case abstract class can have protected members).

Comments

3

To follow up @Fenton explanation in Using a Class as an Interface

There is more explicit way to state class SpecialTest implements Test by using InstanceType<T> utility type. e.g. class SpecialTest implements InstanceType<typeof Test>. I find it less confusing when reading code.

Prefer interfaces in your own code, this magic I've only found useful/necessary when I needed to create a decorator for a 3rd-party class.

1 Comment

God bless you for this. This saved my day
0

in the post you linked to this did you do the last part of it in your implementation:

It should be noticed that any class can be used as an interface in >TypeScript. So if there's no real need to differentiate between interface >and implementation, it may be just one concrete class:

export class Foo {
    bar() { ... }

    baz() { ... }
 }

...
provider: [Foo]
...

Which may be used later as an interface if necessary:

 export class SomeFoo implements Foo { ... }

 ...
 provider: [{ provide: Foo, useClass: SomeFoo }]
 ...

1 Comment

The Provider is something angular specific isn't it? ( you shpuld provide the classes to your components and modules, when the class is an injectable, so it's gets added too the providerpool of the injector)

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.