Search code examples
typescriptclassmethodsprototypefactory

How do I write an interface for a class with prototype methods in TS?


I've been working around this problem for a bit now and figured I would understand eventually or come across a decent explanation. I've read many articles in the TS handbook, google()'d it, and searched SO, but haven't found an answer yet.

I need to know how to properly, and fully, type a prototype method in a TS class/interface.

interface UploadFileInterface {
    contentSizes: number[];
    chunks: [];
    chunksDone: number;
    getChunkLength: (id: number) => void;
    name: string;
    size: string;
    chunksQuantity: number;

}

class UploadedFile implements UploadFileInterface {

    contentSizes: Array<number> = [];
    chunks: [] = [];
    chunksDone: number = 0;
    getChunkLength: (id: number) => void;
     ^^^^^^^^ - 'Property 'getChunkLength' has no initializer and is not definitely assigned in the constructor.'

    constructor(
        public name: string, 
        public size: string, 
        public chunksQuantity: number
        ) {}

}

UploadedFile.prototype.getChunkLength = function(id: number): void {
    
}

I get this dreaded 'Property 'getChunkLength' has no initializeer and is not definitely assigned in the constructor.' error.

I have tried to move the getChunkLength off the class & interface entirely and just leave it how it's written on the prototype (since the interface is supposed to describe only the 'instance side' of the class), but then the error moves to the function on the prototype:

interface UploadFileInterface {
    contentSizes: number[];
    chunks: [];
    chunksDone: number;
    //getChunkLength: (id: number) => void;
    name: string;
    size: string;
    chunksQuantity: number;

}

class UploadedFile implements UploadFileInterface {

    contentSizes: Array<number> = [];
    chunks: [] = [];
    chunksDone: number = 0;
    //getChunkLength: (id: number) => void;

    constructor(
        public name: string, 
        public size: string, 
        public chunksQuantity: number
        ) {}

}

UploadedFile.prototype.getChunkLength = function(id: number): void {
    
}

I find it strange I can't seem to find more threads about this scenario, like it's something that's obvious or directly explained in the handbook, but I can't seem to find it. Are you just not supposed to add things directly to the prototype in TS? I've even tried using Object.setPrototypeOf() in the constructor to see if it would accept it, but no luck.

What am I doing wrong? Should I ONLY use classes in TS and put the methods directly on the class? If that's the case, how do factory functions fit into TS? Can someone show me the different techniques on getting around these situations?

TS Playground


Solution

  • Are you just not supposed to add things directly to the prototype in TS?

    Typescript expects that you'll use the class syntax to define your classes. This syntax may not explicitly mention the prototype, but the functions are being added to the prototype none the less.

    So the following example will add a getChunkLength function to the prototype:

    class UploadedFile implements UploadFileInterface {
        contentSizes: Array<number> = [];
        chunks: [] = [];
        chunksDone: number = 0;
    
        constructor(
            public name: string, 
            public size: string, 
            public chunksQuantity: number
        ) {}
    
        getChunkLength(id: string): void {
        
        }
    }
    

    If you have an unusual case where you need to explicitly modify the prototype, then typescript won't be able to tell what you're doing, and you'll need to assert that the property really is defined. This is done with a !:

    class UploadedFile implements UploadFileInterface {
    
        contentSizes: Array<number> = [];
        chunks: [] = [];
        chunksDone: number = 0;
        getChunkLength!: (id: number) => void;
    
        // ... constructor omitted
    }
    
    UploadedFile.prototype.getChunkLength = function(id: number): void {
        
    }
    

    how do factory functions fit into TS?

    You can do so with a static function on the class:

    class Example () {
      public static createInstance() {
        return new Example();
      }
    
      private constructor() {}
    }
    
    // used like:
    const example1 = Example.createInstance();
    

    Or if you don't need to keep the constructor private, you can just have the factory be a helper function outside the class:

    const createInstance = () => new Example();
    
    class Example() {}