Robba Robba - 2 months ago 25
TypeScript Question

Proper use of a Typescript .d.ts file

I'm trying to get my head around how exactly TypeScript .d.ts files work. I'm having a hard time grasping differences that seem to exist between:


  1. The compiler

  2. Intellisense (in VS2015 with ReSharper)

  3. Runtime / the module loader (Systemjs)



I'm using the oidc-client-js library which supplies an oidc-client.d.ts file that looks a bit like below:

declare var UserManager: Oidc.UserManager;
... more stuff

declare module "oidc-client" {
export = Oidc;
}

declare namespace Oidc {
interface OidcClient {
... more stuff
}
interface OidcManager extends OidcClient {
... more stuff
}
... more stuff
}


I want to use the UserManager class in my own TypeScript class and thus do the following:

/// <reference path="../node_modules/oidc-client/oidc-client.d.ts"/>
export class SecurityService {
private manager: Oidc.UserManager;

constructor() {
this.manager = new Oidc.UserManager({
... settings here
});
}
}


At this point the following happens (the
<reference>
is pointing to the correct file):


  1. The compiler will complain that there is no Oidc namespace on the
    new Oidc.UserManager()
    call

  2. Intellisense also complains about the missing Oidc symbol

  3. Doesn't compile, so also doesn't run



So in an effort to please the compiler I can change my code to this (notice the change from
new Oidc.UserManager
to
new UserManager
:

/// <reference path="../node_modules/oidc-client/oidc-client.d.ts"/>
export class SecurityService {
private manager: Oidc.UserManager;

constructor() {
this.manager = new UserManager({
... settings here
});
}
}


This has the following effect:


  1. The compiler will no longer complain

  2. Intellisense is also happy

  3. When running the app, I get an error stating that UserManager does not exist. This is correct because in the oidc-client-js library all the classes are actually in the Oidc namespace. So the correct way to create an object is
    new Oidc.UserManager()
    .



Another way to fix the issue is to change the code into this (note the added
declare var Oidc
:

/// <reference path="../node_modules/oidc-client/oidc-client.d.ts"/>
declare var Oidc;

export class SecurityService {
private manager: Oidc.UserManager;

constructor() {
this.manager = new Oidc.UserManager({
... settings here
});
}
}


This will:


  1. Make the compiler happy

  2. Trick intellisense into thinking that anything under Oidc is of type
    any
    , which is obviously less than optimal.

  3. Work fine at run time



The only solution I've found so far that actually works on all fronts is to change the .d.ts file to use classes instead of interfaces:

declare var UserManager: Oidc.UserManager;
... more stuff

declare module "oidc-client" {
export = Oidc;
}

declare namespace Oidc {
class OidcClient {
... more stuff
}
class OidcManager extends OidcClient {
... more stuff
}
... more stuff
}


But this requires a change in the definition file. Looking at other definition files they usually use interfaces instead of classes so this makes me wonder if this is actually an issue with the definitions file or if this is just my lack of proper understanding of how this works in TypeScript.

Any help would be appreciated.

Answer

I think it's just an issue with the definition file.

The definition file says that there should be a var UserManager: Oidc.UserManager available. This is why the compiler doesn't complain when you use new UserManager(), because the definition file says that it exists.

The problem is that it actually does not exist (in the JavaScript), hence the runtime error.

This file might be incorrect because it's outdated. I've found a seemingly updated definition file, which you might want to give a try.