52

I have defined the following interface in typescript:

interface MyInterface {
    () : string;
}

This interface simply introduces a call signature that takes no parameters and returns a string. How do I implement this type in a class? I have tried the following:

class MyType implements MyInterface {
    function () : string {
        return "Hello World.";
    }
}

The compiler keeps telling me that

Class 'MyType' declares interface 'MyInterface' but does not implement it: Type 'MyInterface' requires a call signature, but Type 'MyType' lacks one

How can I implement the call signature?

3
  • For what it's worth, I filed an issue related to this. Commented Oct 7, 2012 at 15:43
  • In this case MyInterface is acting a bit like typedef: it defines a function that is always returning string. This is a type that is subtype of function and can be used to assign type to anything that is a function, take no params and return string. A new type. Classes produce in JS the classic pattern and calling them produces Object, so a class cannot implement this interface. Basically only functions can when they are not called as constructors (i.e. with new). Commented Oct 8, 2012 at 6:55
  • There is similar question with the solution : stackoverflow.com/questions/13136892/overload-implementation/… Commented Oct 30, 2012 at 14:39

3 Answers 3

22

In case if callable interface should have other methods you can do it like this:

interface Greeter {
    (): void;
    setName(name: string): void;
}

class ConsoleGreeter {

    private constructor( // constructable via `create()`
        private name = 'world'
    ) {}

    public call(): void {
        console.log(`Hello ${this.name}!`);
    }

    public setName(name: string) {
        this.name = name;
    }

    public static create(): Greeter {
        const instance = new ConsoleGreeter();
        return Object.assign(
            () => instance.call(),
            {
                setName: (name: string) => instance.setName(name)
                // ... forward other methods
            }
        );
    }
}

const greeter = ConsoleGreeter.create();
greeter(); // prints 'Hello world!'
greeter.setName('Dolly');
greeter(); // prints 'Hello Dolly!'

Downside: greeter instanceof ConsoleGreeter is false

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

4 Comments

Why did you decide to write all function callers in the Object.assign invocation? Why didn't you just write Object.assign(instance.call, instance);?
@RobertKoritnik I did so for simplicity of example. It is possible to introduce a generic solution which doesn't require to forward every instance method, but it will be not as simple as you propose... 1. Need to bind call method to an instance, otherwise this will be undefined when you invoke call. 2. Object.assign copies all enumerable OWN properties of a source object, but instance inherits all methods through a prototype chain.
Yeah I realised the same when I was trying to implement a custom angularjs IHttpService which also has the implicit call signature. It is interesting though that the first argument of the Object.assign is a function and not an object. This almost feels as if one would create a class (constructor function) with static members (not part of the prototype, but part of the constructor directly).
why public call(): void { has be public ?
17

Classes can't match that interface. The closest you can get, I think is this class, which will generate code that functionally matches the interface (but not according to the compiler).

class MyType implements MyInterface {
  constructor {
    return "Hello";
  }
}
alert(MyType());

This will generate working code, but the compiler will complain that MyType is not callable because it has the signature new() = 'string' (even though if you call it with new, it will return an object).

To create something that actally matches the interface without the compiler complaining, you'll have to do something like this:

var MyType = (() : MyInterface => {
  return function() { 
    return "Hello"; 
  }
})();
alert(MyType());

9 Comments

Why not var MyType : MyInterface = function() { return "hello";}; then? I think OP's interface actually says that the implementer should be a function.
@kahoon I assumed that OP was wanting the implementor to be a function returning a string.
@PeterOlson So what's the point in having CallSignatures in interface definitions at all?
@Valentin the main purpose of having call signatures in interface definitions is so that you can interoperate with existing javascript code.
Actually you don't neet to create such complex declaration :-) I mean that nested anonymous function is not necessary in that case.
|
9

The code examples in this answer assume the following declaration:

var implementation: MyInterface;

Providing an implementation of a callable interface

As a follow-up to the accepted answer, as suggested by some of its commentors, a function that matches the interface's call signature implicitly implements the interface. So you can use any matching function as an implementation.

For example:

implementation = () => "Hello";

You don't need to explicitly specify that the function implements the interface. However, if you want to be explicit, you can use a cast:

implementation = <MyInterface>() => "Hello";

Providing a reusable implementation

If you want to produce a reusable implementation of the interface like you normally would with a Java or C# interface, just store the function somewhere accessible to its consumers.

For example:

function Greet() {
    return "Hello";
}

implementation = Greet;

Providing a parameterised implementation

You may want to be able to parameterise the implementation in the same way that you might parameterise a class. Here's one way to do this:

function MakeGreeter(greeting: string) {
    return () => greeting;
}

implementation = MakeGreeter("Hello");

If you want the result to be typed as the interface, just explicitly set the return type or cast the value being returned.

Comments

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.