Adding Custom Typings Files (*.d.ts) In An Angular 2 TypeScript Application
In an Angular 2 application that uses TypeScript, the TypeScript compiler can extract type APIs from your *.ts files. But sometimes, you need to tell the TypeScript compiler to expect "ambient values" that are provided outside of the scope of the known *.ts files. To do this, you have to install a custom Typings file - *.d.ts - in your Angular 2 application. Doing this, at least for me as a TypeScript novice, was not straightforward. And, no amount of Googling seemed to provide an answer. Luckily, I just happened to come across a blog post by George Dyrrachitis on using TypeScript with Angular 2. In his post, George demonstrates how to install custom Typings files. All credit for my post goes to him! I'm just reiterating some of what he wrote in order to burn the concept into my brain meat.
CAUTION: Like I said above, I'm a novice when it comes to TypeScript; so, I will be careful to not misuse terminology. But, it's definitely possible that I will be making mistakes. Take this post with a grain of salt.
When using TypeScript 2.x, the TypeScript compiler will automatically resolve types by pulling type definition files out of your @Types node-modules folder (assuming you've installed @Types). As such, the typings.json configuration file in your Angular 2 application may not start out with any dependencies:
{
"globalDependencies": {}
}
Even though there are no dependencies, running a typings install on this will still result in a "typings" folder, which we can reference in our TypeScript configuration file:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"typeCheck": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"files": [
"typings/index.d.ts"
]
}
Notice that we are explicitly telling the in-browser TypeScript compiler to fetch the root TypeScript file in the "typings" folder. This will, in turn, pull in any type definition files that were installed using typings.
Now, if we use relative file paths in our Angular 2 components, we need to define the "moduleId" in the component's meta-data. For example, in this simple App component, I'm using relative paths for the templateUrl and styleUrls meta-data properties:
// Import the core angular services.
import { Component } from "@angular/core";
@Component({
// This value needs to be set so that the Angular 2 compiler will know how to
// interpret the subsequent templateUrl and styleUrls relative paths.
moduleId: module.id,
selector: "my-app",
templateUrl: "./app.component.htm",
styleUrls: [ "./app.component.css" ]
})
export class AppComponent {
public counter: number;
// I initialize the component.
constructor() {
this.counter = 0;
}
// ---
// PUBLIC METHODS.
// ---
// I increment the counter.
public increment() : void {
this.counter++;
}
}
If we run this application using the in-browser compiler, it will work; but, the TypeScript loader will complain that it has no idea what this "module.id" value is:
To remedy this, we have to install a custom Typings file that tells the TypeScript compiler about the API of the globally-available "module" object. To do this, we can create a custom *.d.ts type definition file for our application. For this demo, I'm going to put this file in a new folder, app-typings:
/app-typings/app.d.ts
// In order to use relative paths for the Component `templateUrl` and `styleUrls`, we
// need to tell the TypeScript compiler that there is an ambient value "module". This
// way, it won't report errors in every component that uses relative paths.
declare var module: { id: string };
This Type definition file tells the TypeScript compiler that there is a "module" type that has an "id" property (of type String).
At this point, neither the Angular 2 application nor the TypeScript compiler knows about this file; so, we have to install it as a custom Typings file. Since I installed the Typings dependency using npm, I am going to reach into the node-modules to find the Typings executable:
./node_modules/.bin/typings install --global --save** file:./app-typings/app.d.ts**
Here, we're telling Typings to take the custom type definition file that we just created and install it as part of the typing echosystem for our application. When we do this, Typings will copy the app.d.ts file into the "typings" folder. But, it will also update our typings.json configuration file thanks to the "--save" option:
{
"globalDependencies": {
"app": "file:./app-typings/app.d.ts"
}
}
At this point, now that the typings.json file is updated (and committed to your project's code repository), you should theoretically be able to delete the "typings" folder and re-run the generic install:
./node_modules/.bin/typings install
You don't need to do this; I'm just demonstrating that you can do this in order to feel confident that your custom type definition file will get automatically installed by your continuous delivery server (if you use one) during deployment.
Now that we have the custom app.d.ts type definition file installed, we can re-run our Angular 2 application:
As you can see this time, there is no error about the "module.id" file. That's because the TypeScript compiler knows about our custom typings file which, in turn, tells it to expect the ambient module.id value.
For those of you who are familiar with TypeScript, this whole process might be painfully obvious. But, for people like me - who are dealing with TypeScript for the first time when learning Angular 2 - nothing about the Typing ecosystem is obvious. Especially not creating and including custom type definition files. Hopefully this post will help anyone else who - like myself - tried to Google for advice on custom type definitions files and continually came up empty.
Want to use code from this post? Check out the license.
Reader Comments
In case you just want to compile successfully the expression "module.id" you can install the NodeJS typings by running
typings install dt~node --global
It offers the following definitions
interface NodeModule {
exports: any;
require: NodeRequireFunction;
id: string;
filename: string;
loaded: boolean;
parent: any;
children: any[];
}
declare var module: NodeModule;
Which quite the same as you defined.
If you mentioned module.id just as an example of how you can fix compilation errors using custom typings installation then forget what I've just said ...
@Ori,
This is awesome. I didn't realize that there was an existing Type that would allow for the `module.id` stuff. Like I said, I'm a total novice with all this stuff. So, to your question - you are half right. Meaning, I used the `module.id` as a showcase for a custom type definition file. But, the reality is, it's the only reason - so far - that I've had to create a custom typings file. At the end of the day, knowing about this other existing interface would have solved my problem :)
please note, typings (along with tsd) tool is deprecated. npm package scope @types ir the way to go.
@John,
I tried to call that out (sort of) in the post. But, how would you create a custom type definition file if only the npm-based approach is available? You would never put custom code into the @Types npm folder (as that would / could get overwritten). So, you would still need some way to say, here is a type-definition file?
You can also bypass all that just import your .d.ts directly.
What I do is dump all my TS "shut-ups" into one file called
app.d.ts and in my main file App.ts I load it directly via:
///<reference path="../typings/app.d.ts" />
You can see my app.d.ts at:
https://github.com/born2net/studioDashboard/blob/master/typings/app.d.ts
usually inside my app.d.ts I would refference other definition files as well.. hey, whatever makes TypeScript happy :)
Angular 2 Kitchen sink: http://ng2.javascriptninja.io
and source@ https://github.com/born2net/Angular-kitchen-sink
Regards,
Sean
I also found in my case that just providing a ".d.ts" file with the interface was sufficient. Not sure if webpack or tsc picks up on these files but I did not need to reference the file via ///<reference path="../typings/app.d.ts" />
@Sean,
Ah, I see - and then it looks like are you explicitly pulling that "app.d.ts" file in with your tsconfig.json file. With a recent update to the TypeScript Plugin for System.js, I had to do something similar. I was installing types using the @Types npm module; but, the in-browser TypeScript plugin wasn't finding them. So, in the tsconfig-inspired settings in System.config.js, I had to explicitly pull in the external types using the "files" value (kind of like your "filesGlob" stuff, except I have to be more explicit for the in-browser stuff).
@Al,
I think my issues is that since I am doing in-browser transpiling, the TypeScript plugin can't just "scan" the file system looking for *.d.ts. As such, I have to explicitly tell it which files to make HTTP requests for.
right on...
In an effort to gain deeper knowledge into Angular 2 I wish someone would create an in depth explanation / tutorial on the underlying structure of components, directives and their containers and views.
As per the docs:
The component's container can contain two kinds of Views. Host Views, created by instantiating a Component via createComponent, and Embedded Views, created by instantiating an Embedded Template via createEmbeddedView.
The location of the View Container within the containing View is specified by the Anchor element. Each View Container can have only one Anchor Element and each Anchor Element can only have a single View Container.
Root elements of Views attached to this container become siblings of the Anchor Element in the Rendered View.
This leaves a lot of open questions, such as: a Host view is referring to the element that Component resides in, and an Embedded view is referring to the component's template itself?
Is that true for both cases when created manually (via createComponent and createComponent) as well as when created declaratively via <SomeComponentTag/> in another hosting component (parent)?
Is that the case for Directives as well which don't have a template (thus no view)? And how all this works in a Shadow dom environment (browser actually support a component host) vs an emulated environment?
Angular 2 does do a lot of magic and in an effort to become an expert I wish to better understand, (maybe via a visual diagram) the entire relationship of: ViewContainerRef, Host views, Templates, Embedded Template, ViewChild, ViewContainer and their hierarchy of components and directives.
I consider myself extremely well versed in Angular2 (delivered 2 huge project already) but still feel I have holes in my understand of the underline internal workings.
Sure you don't need to know how a car works to drive one, but you handle it much better if you do,
Posted: http://stackoverflow.com/questions/40423772/angular-2-how-underline-framework-works-with-relation-to-containers-hosts-and
Angular 2 Kitchen sink: http://ng2.javascriptninja.io
and source@ https://github.com/born2net/Angular-kitchen-sink
Regards,
Sean
To add a little detail to @John's comment,
Angular 2 has deprecated this typings approach - https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta6-2016-02-11.
If you'd like to install an npm package without typescript support, the process is easier now. Just install from npm like any other package inside your project:
> npm install --save lodash @types/lodash
This one line installs two packages - lodash itself and also the @types package for lodash.
We no longer have the extra typings folder cluttering up our project; tsd and typings are now behind us.
More details here... https://blogs.msdn.microsoft.com/typescript/2016/06/15/the-future-of-declaration-files/?
@All,
I wanted to dig a little more into Type Declaration files now that "Typings" are gone and I'm getting more comfortable with TypeScript itself:
www.bennadel.com/blog/3386-note-to-self-adding-type-declaration-files-to-a-typescript-2-6-2-project.htm
In this post, I just walk through a couple of scenarios in which I have an existing ".js" file and I need to add a Type Declaration file for the compiler. It's primarily a note-to-self, but it might be interesting to others.