3

I've created a custom Knockout extender, and I'm having trouble extending the existing interface provided by a definition file for Knockout.

Extenders/Numeric.ts

import * as ko from "knockout";

function Extender(target: KnockoutObservable<number>, options: IOptions = {}): KnockoutObservable<number> {
    // ...
};

interface IOptions {
    // ...
}

export {Extender as NumericExtender, IOptions as INumericExtenderOptions}

Boot.ts

import * as ko from "knockout";
import {NumericExtender} from "./Extenders/Numeric";

class Boot {    
    public constructor() {
        ko.extenders.numeric = NumericExtender;
    }
}

To let the compiler about ko.extenders.numeric, I need to extend the existing interface:

interface KnockoutExtenders {
    numeric(target: KnockoutObservable<number>, options?: INumericExtenderOptions): KnockoutObservable<number>;
}

Now here I run into trouble. In order to access INumericExtenderOptions, I need an import statement:

import {INumericExtenderOptions} from "./Extenders/Numeric";

But when an import statement is added, the file is considered to be a module, which makes it impossible to extend an existing interface.

Is there a way to do this, or will I need to move IOptions to the definition file in order to avoid an import?

Community
  • 1
  • 1
user247702
  • 23,641
  • 15
  • 110
  • 157

1 Answers1

2

It seems like you are using the global version of the knockout declaration file. I don't think it is possible to extend an interface defined in the global scope from within a module declaration file. There are several solutions:

  • I think the easiest solution is to put your interface into the global namespace as well. For IOptions this becomes:

    // index.d.ts
    interface IOptions {
      // ¯\_(ツ)_/¯
    }
    
    interface KnockoutExtenders {
      numeric(target: KnockoutObservable<number>, options?: IOptions): KnockoutObservable<number>;
    }
    

    Now you can access IOptions and KnockoutExtenders with a numeric function anywhere, as the declaration file is still global.

  • Another solution would be to pull in the module version (I think this is my preferred solution, just because your not polluting the global namespace with all the knockout types). In the case of knockout: typings install --save knockout. Then you would have to specifically import the types you need, whenever you need them. Eg. your numeric.ts becomes

    // src/numeric.ts
    import { Observable } from "knockout"
    
    export function Extender(target: Observable<number>, options: IOptions = {}): Observable<number> {
      // ¯\_(ツ)_/¯
    };
    
    export interface IOptions {
      // ¯\_(ツ)_/¯
    }
    

    Then you can augment the knockout module in another declaration file. eg:

    // index.d.ts
    import { Observable } from "knockout"
    import { IOptions } from './src/numeric'
    
    declare module "knockout" {
      interface Extenders {
        numeric(target: Observable<number>, options?: IOptions): Observable<number>
      }
    }
    

    Then you should be able to use the augmented Extenders interface anywhere in your application:

    // src/boot.js
    import { extenders } from "knockout"
    import { Extender } from "./numeric"
    
    class Boot {
      public constructor() {
        extenders.numeric = Extender
      }
    }
    
  • A final solution, probably best solving your problem, is to use a modular declaration file, but to augment the global module. Your numeric.ts stays the same, and your declaration file becomes:

    import { IOptions } from './src/numeric'
    
    declare global {
      interface KnockoutExtenders {
        numeric(target: KnockoutObservable<number>, options?: IOptions): KnockoutObservable<number>
      }
    }
    

For more information, have a look at the page on declaration merging from the typescript handbook.

Pelle Jacobs
  • 2,379
  • 1
  • 21
  • 25
  • Managed to find some time to go over this, I ended up using the third solution. There's no module version of Knockout available at this time, so the second solution didn't work for this case. – user247702 Aug 30 '16 at 13:45
  • Weird. `typings install knockout` for sure installs a module knockout declaration file. But maybe this is not the file that you want? – Pelle Jacobs Aug 30 '16 at 19:18
  • I mean the actual library :) not the typings. Unless I misunderstand things and there's a way to "modularize" a library? – user247702 Aug 30 '16 at 19:44
  • Same with me, third solution's great! Exactly what I needed. (and odd it isn't mentioned in more places) – Venryx Jan 01 '17 at 16:40