37

I installed the knockout definitions using the documented method like this.

npm install @types/knockout

It works nicely, I could import it like this anywhere.

import * as ko from "knockout";

However, I'm stuck with extending KnockoutStatic interface with some custom stuff. I'm trying to migrate a <reference ... /> and namespace based huge TS application to use modules. Before, I easily declared the extension interface anywhere and the declarations got merged. Let's say my extension looks like this.

interface KnockoutStatic {
  doSomething(): void;
}

I tried to create a KnockoutExtensions.d.ts file where I declared it like this.

import "knockout";

declare module "knockout" {
  export interface KnockoutStatic {
    doSomething(): void;
  }
}

But when I import both knockout and my extension somewhere, TS still cannot resolve the doSomething calls.

import * as ko from "knockout";
import "./KnockoutExtensions";

ko.doSomething(); // error

What is the proper method of extending library interfaces using TypeScript 2.0 and the new d.ts subsystem?

I'm using Visual Studio 2015 Update 3 with TypeScript 2.0 installed.

Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93

6 Answers6

22

You can easily extend the 'knockout' or any other TypeScript namespace.

Example: create knockout-extension.d.ts file

/// <reference path="<path-to-typings-dir>/knockout/index.d.ts" />

declare module 'knockout' {

  export interface CustomType {

    customField: string;

    customMethod(arg1: number, arg2: boolean): boolean;
  }

  namespace customNamespace {

    export interface AnotherCustomType {
      customField1: string;
      customField2: boolean;
    }
  }

  // NOTE: extending existing interface
  export interface KnockoutStatic {
    customMethod(): void;
  }
}

Note: ensure that this file is picked-up by the TypeScript compiler.

Use the newly defined types from the extended module.

// one way
import { CustomType } from 'knockout';

const foo: CustomType;

// second way
import * as kc from 'knockout';

const foo: kc.CustomType;
const bar: kc.customNamespace.AnotherCustomType;

For more info on modules and namespaces you can check TypeScript documentation on Modules and Namespaces and using them together.

Cheers!

S.Klechkovski
  • 4,005
  • 16
  • 27
15

I found that winston has the same problem, using the export = syntax. I found this page helpful when it showed that react did the same thing: https://www.credera.com/blog/technology-solutions/typescript-adding-custom-type-definitions-for-existing-libraries/.

The solution they recommended, which I found worked is this:

import 'react';

declare module 'react' {
    interface OlHTMLAttributes<T> {
        type?: "1" | "a" | "A" | "i" | "I";
    }
}

Simply importing the module, and then declaring it allows any interfaces in this declaration block to extend the existing ones and in other parts of you code you can go on using the interface as you normally would; i.e. you would still import react or winston or knockout and you'd see these new interface members. You don't have to start referencing a custom interface or anything like that.

cjbarth
  • 4,189
  • 6
  • 43
  • 62
3

The problem is that knockout typing file uses the export = syntax and it's not "augmentation friendly". See this as a reference.

The simplest solution for me was to wrap the extensions in declare global { } as knockout typing file declares everything in global scope.

declare global {
  interface KnockoutStatic {
    doSomething(): void;
  }
}
Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93
1

This code works for me

// observable.ts
export class Observable<T> {
  // ... implementation left as an exercise for the reader ...
}

// map.ts
import { Observable } from "./observable";
declare module "./observable" {
  interface Observable<T> {
    map<U>(f: (x: T) => U): Observable<U>;
  }
}
Observable.prototype.map = function (f) {
  // ... another exercise for the reader
};

// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());
surinder singh
  • 1,463
  • 13
  • 12
1

I was trying to extend the Express Request object so that I can use req.log.info to log something. The following settings works for me

// tsconfig.json
{
  "compilerOptions": {
    ...,
    "baseUrl": ".",
    "paths": {
      "*": ["node_modules/*", "types/*"]
    }
  },
  "include": ["src/**/*"],
  "files": ["./types/express-extension.d.ts"],
  "ts-node": {
    "files": true
  }
}

// types/express-extension.d.ts
import 'express';

declare module 'express' {
  export interface Request {
    log?: {
      debug: (str: string) => void,
      info: (str: string) => void,
      warn: (str: string) => void,
      error: (str: string) => void,
    }
  }
}

My file structure looks like this:

src
|_ ...
types
|_ express-extension.d.ts
tsconfig.json
package.json
anniex
  • 326
  • 3
  • 7
-2

You need to create your interface outside of your module. Do not declare it with export.

module example  {
   //...do stuff
}
 
interface KnockoutStatic {
  doSomething(): void;
}

You can add it in a separate file like where you add your interface extensions to keep it clean.

javad bat
  • 4,236
  • 6
  • 26
  • 44
seven
  • 414
  • 4
  • 12