16

I'm trying here to extend a namespace from package typings, @typings/fullcalendar.

/// <reference path="./types/fullcalendar" />

import * as fullcalendar from 'fullcalendar';
import { TimeGrid } from 'fullcalendar';

// TimeGrid and fullcalendar.views are used then

Originals typings can be seen here.

And fullcalendar-custom.d.ts is

import * as FC from 'fullcalendar';

export as namespace FC;

declare class TimeGrid { prepareHits() }

declare let views: any;

This results in type errors, so it is obvious that fullcalendar namespace wasn't extended properly:

TS2305: Module '".../node_modules/@types/fullcalendar/index"' has no exported member 'TimeGrid'.

TS2339: Property 'views' does not exist on type 'typeof ".../node_modules/@types/ fullcalendar/index"'.

How should this be done the right way?

Can reference directive be avoided here, considering that types directory is specified in typeRoots?

The application is bundled with Webpack and awesome-typescript-loader, so the behaviour may differ from other compilation methods. At some point types seemed to be ok in IDE inspections (WebStorm) but still got type errors on compilation.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565

4 Answers4

22

We can import a namespace in a non-declaration .ts, and export it again as a extended type:

// custom-fc.ts : enhances declaration of FC namespace
import * as origFC from "fullcalendar";

declare namespace Complimentary {
    class TimeGrid {
        prepareHits(): void;
    }
    let views: any;
}

// apply additional types to origFc and export again
export const FC: (typeof Complimentary & typeof origFC) = origFC as any;

 

// use-fc.ts : consumer of extended declaration
import { FC } from "./custom-fc";

console.log(FC.TimeGrid);
console.log(FC.views);

(This somehow differs from your scenario, in that I'm using @types/ packages and webpack ts-loader, but you should be able to do something similar.)

Jokester
  • 5,501
  • 3
  • 31
  • 39
  • Thanks, I've tried to avoid reexporting if possible since I didn't extend the package itself in the place where I extend typings, but it does the job. I ended up with `declare class Complimentary ... export default origFC; `. A namespace was ok for TS but caused some type issues for IDE. – Estus Flask Jul 02 '17 at 17:31
  • 4
    Not working in TS3? export default original; Cannot use namespace 'original' as a value. Cannot use namespace 'extended' as a type. – Crusader Apr 20 '20 at 21:51
  • 1
    Is there a way to extend original namespace without changing its name? – jayarjo May 21 '20 at 09:00
7

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

Example: create fullcalendar-extension.d.ts file

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

declare module 'fullcalendar' {

  export interface TimeGrid {

    customField: string;

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

    prepareHits();
  }

  namespace customNamespace {

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

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

Use the newly defined types from the extended module.

// one way
import { TimeGrid } from 'fullcalendar';

const timeGrid: TimeGrid;

// second way
import * as fc from 'fullcalendar';

const timeGrid: fc.TimeGrid;
const anotherTimeGrid: fc.customNamespace.AnotherTimeGrid;

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
  • But how `customNamespace` is supposed to be recognized in 'one way'? I'm trying to do `import { TimeGrid } from 'fullcalendar'; class CustomGrid extends TimeGrid { ... }`, and TimeGrid and its methods aren't recognized from the interface (and I don't see the way how it's going to work in 'second way'). – Estus Flask Jul 02 '17 at 16:45
  • In the given example TimeGrid is defined as an interface, the class CustomGrid can only implement it. If TimeGrid is a class that can be extended in the fullcalendar lib, then declare it as a class in the fullcalendar-extension.d.ts file. It's easy as replacing the 'interface' keyword with 'class'. – S.Klechkovski Jul 02 '17 at 18:50
  • About the imports, due to the limited object destructuring capabilities in ES6 modules, 'customNamespace' can only by used as shown in the 'second way'. – S.Klechkovski Jul 02 '17 at 18:54
1

Using latest TypeScript v4, when I had issues with combining external files and libs with Svelte I did this:

// root/my-declarations.d.ts

import { SomeNameSpace as SomeNameSpaceOriginal} from '../../any-relative-path/to/ts-file';
import {SvelteComponentDev} from 'svelte/internal';

declare namespace SomeNameSpaceExtended {
    function setValue(value:any):any
    let UI:SvelteComponentDev
    let abc:string
}

declare global {
    //this will add new items to top-level (root)
    //note: in my case, only importing "as SomeNameSpaceOriginal" gave me correct typing later using our new extended global SomeNameSpace
    let SomeNameSpace: typeof SomeNameSpaceOriginal & typeof SomeNameSpaceExtended;

    //this will add new items to window:
    interface Window {
        elementInside:'window.elementInside'
    }
}

Upvoted Answer in combination with https://stackoverflow.com/a/63973683 gives most understanding about how to extend things around.

dadiborn
  • 11
  • 2
0

add jsconfig.json or tsconfig.json to your project add any .d.ts file to your project

add code to declaration file

import 'fullcalendar';

declare global {
    namespace FullCalendar {
        export * from 'fullcalendar';
    }
}

and then you can use this type under the namespace defined

Asaf
  • 949
  • 2
  • 13
  • 14