2

I am trying to get the Typescript correct for a constructor and factory function, but am still stuck with the below errors when I run it through tsc.

src/tests/lib/mockNconf.ts(16,14): error TS2322: Type '(conf: Conf) => MockNconf' is not assignable to type 'MockNconfConstructor'.
  Type '(conf: Conf) => MockNconf' provides no match for the signature 'new (conf: Conf): MockNconf'.
src/tests/lib/mockNconf.ts(18,12): error TS2350: Only a void function can be called with the 'new' keyword.

I have looked at How to define a Typescript constructor and factory function with the same name?, but that relates only to the typescript definition (with a pure javascript implementation).

The code I have is

import * as deepclone from 'lodash.clonedeep';

type Conf = { [key: string]: any };

interface MockNconf {
  set(key: string, data: any): void,
  get(key: string): any,
  clone(): MockNconf
}

interface MockNconfConstructor {
  new (conf: Conf): MockNconf;
  (conf: Conf): MockNconf;
}

export const MockNconf: MockNconfConstructor = function MockNconf(conf: Conf): MockNconf {
  if (!(this instanceof MockNconf)) {
    return new MockNconf(conf);
  }

  this.conf = conf;
};

Object.assign(MockNconf.prototype, {
  set: function set(key: string, data: any): void {
    if (typeof data === 'undefined') {
      if (typeof this.conf[key] !== 'undefined') {
        delete this.conf[key];
      }
    } else {
      this.conf[key] = data;
    }
  },
  get: function get(key: string): any {
    return this.conf[key];
  },
  clone: function clone(): MockNconf {
    const newConf = deepclone(this.conf);

    return new MockNconf(newConf);
  }
});

export default MockNconf;

(In playground)

2 Answers 2

2

Thanks to Titian Cernicova-Dragomir for helping to solve. It can be done without the use of classes, but the MockNconf can't be cast when it is first defined, instead it needs to be cast when it's used in the constructor function and when it is exported.

import * as deepclone from 'lodash.clonedeep';

type Conf = { [key: string]: any };

interface MockNconfInterface {
  set(key: string, data: any): void,
  get(key: string): any,
  clone(): MockNconfInterface
}

interface MockNconfConstructor {
  new (conf: Conf): MockNconfInterface;
  (conf: Conf): MockNconfInterface;
}

const MockNconf = function MockNconf(conf: Conf): MockNconfInterface {
  if (!(this instanceof MockNconf)) {
    return new (<MockNconfConstructor>MockNconf)(conf);
  }

  this.conf = conf;
};

Object.assign(MockNconf.prototype, {
  set: function set(key: string, data: any): void {
    if (typeof data === 'undefined') {
      if (typeof this.conf[key] !== 'undefined') {
        delete this.conf[key];
      }
    } else {
      this.conf[key] = data;
    }
  },
  get: function get(key: string): any {
    return this.conf[key];
  },
  clone: function clone(): MockNconfInterface {
    const newConf = deepclone(this.conf);

    return new (<MockNconfConstructor>MockNconf)(newConf);
  }
});

export default MockNconf as MockNconfConstructor;
Sign up to request clarification or add additional context in comments.

Comments

0

There is no way to define a class/function in typescript that can work as both a class and a function. However, we can decalare a regular class, with all the required methods and export it as MockNconfConstructor using a type assertion. The class is represented by the constructor function so the constructor is what will get invoked, and we can use the same code ( this instanceof MockNconf) to check if the invocation happened as a constructor or as a regular function:

class MockNconf {
    conf: { [key: string]: any; };
    constructor(conf: Conf) {
        if (!(this instanceof MockNconf)) {
            return new MockNconf(conf);
        }

        this.conf = conf;
    }
    set(key: string, data: any): void {
        if (typeof data === 'undefined') {
            if (typeof this.conf[key] !== 'undefined') {
                delete this.conf[key];
            }
        } else {
            this.conf[key] = data;
        }
    }
    get(key: string): any {
        return this.conf[key];
    }
    clone(): MockNconf {
        const newConf = deepclone(this.conf);

        return new MockNconf(newConf);
    }
}


export default MockNconf as MockNconfConstructor;

Usage:

import MockNconf from './q48519886';
var foo = MockNconf({ a: 10});
var boo = new MockNconf({a: 10});

7 Comments

Darn. Was hoping that wasn't going to be the case, but I suppose that will have to do. Thank you.
@bytesnz sorry I don't have better news
No worries. Thank you very much for helping
Hrm... that got rid of the tsc issues, but introduced errors when running the tests - "Class constructor MockNconf cannot be invoked without 'new'". Compiled to es6 and running tests using ava and node 8.9.4 (travis-ci.org/bytesnz/marss github.com/bytesnz/marss/tree/class-mock-nconf)
I compiled to es5 and it worked .. my guess is that es6 doesn't allow use of constructor without new ... I'll have a look
|

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.