How can I create an object based on an interface file definition in TypeScript?

I have defined an interface like this:

interface IModal {
    content: string;
    form: string;
    href: string;
    $form: JQuery;
    $message: JQuery;
    $modal: JQuery;
    $submits: JQuery;
 }

I define a variable like this:

var modal: IModal;

However, when I try to set the property of modal it gives me a message saying that

"cannot set property content of undefined"

Is it okay to use an interface to describe my modal object and if so how should I create it?


Solution 1:

If you are creating the "modal" variable elsewhere, and want to tell TypeScript it will all be done, you would use:

declare const modal: IModal;

If you want to create a variable that will actually be an instance of IModal in TypeScript you will need to define it fully.

const modal: IModal = {
    content: '',
    form: '',
    href: '',
    $form: null,
    $message: null,
    $modal: null,
    $submits: null
};

Or lie, with a type assertion, but you'll lost type safety as you will now get undefined in unexpected places, and possibly runtime errors, when accessing modal.content and so on (properties that the contract says will be there).

const modal = {} as IModal;

Example Class

class Modal implements IModal {
    content: string;
    form: string;
    href: string;
    $form: JQuery;
    $message: JQuery;
    $modal: JQuery;
    $submits: JQuery;
}

const modal = new Modal();

You may think "hey that's really a duplication of the interface" - and you are correct. If the Modal class is the only implementation of the IModal interface you may want to delete the interface altogether and use...

const modal: Modal = new Modal();

Rather than

const modal: IModal = new Modal();

Solution 2:

If you want an empty object of an interface, you can do just:

var modal = <IModal>{};

The advantage of using interfaces in lieu of classes for structuring data is that if you don't have any methods on the class, it will show in compiled JS as an empty method. Example:

class TestClass {
    a: number;
    b: string;
    c: boolean;
}

compiles into

var TestClass = (function () {
    function TestClass() {
    }
    return TestClass;
})();

which carries no value. Interfaces, on the other hand, don't show up in JS at all while still providing the benefits of data structuring and type checking.

Solution 3:

If you are using React, the parser will choke on the traditional cast syntax so an alternative was introduced for use in .tsx files

let a = {} as MyInterface;

https://www.typescriptlang.org/docs/handbook/jsx.html

Solution 4:

I think you have basically five different options to do so. Choosing among them could be easy depending on the goal you would like to achieve.

The best way in most of the cases to use a class and instantiate it, because you are using TypeScript to apply type checking.

interface IModal {
    content: string;
    form: string;
    //...

    //Extra
    foo: (bar: string): void;
}

class Modal implements IModal {
    content: string;
    form: string;

    foo(param: string): void {
    }
}

Even if other methods are offering easier ways to create an object from an interface you should consider splitting your interface apart, if you are using your object for different matters, and it does not cause interface over-segregation:

interface IBehaviour {
    //Extra
    foo(param: string): void;
}

interface IModal extends IBehaviour{
    content: string;
    form: string;
    //...
}

On the other hand, for example during unit testing your code (if you may not applying separation of concerns frequently), you may be able to accept the drawbacks for the sake of productivity. You may apply other methods to create mocks mostly for big third party *.d.ts interfaces. And it could be a pain to always implement full anonymous objects for every huge interface.

On this path your first option is to create an empty object:

 var modal = <IModal>{};

Secondly to fully realise the compulsory part of your interface. It can be useful whether you are calling 3rd party JavaScript libraries, but I think you should create a class instead, like before:

var modal: IModal = {
    content: '',
    form: '',
    //...

    foo: (param: string): void => {
    }
};

Thirdly you can create just a part of your interface and create an anonymous object, but this way you are responsible to fulfil the contract

var modal: IModal = <any>{
    foo: (param: string): void => {

    }
};

Summarising my answer even if interfaces are optional, because they are not transpiled into JavaScript code, TypeScript is there to provide a new level of abstraction, if used wisely and consistently. I think, just because you can dismiss them in most of the cases from your own code you shouldn't.

Solution 5:

You can do

var modal = {} as IModal