Using Import = Require Syntax With TypeScript 2.2 In Angular 2.4.9
When using TypeScript, importing non-TypeScript modules has a somewhat irritating "* as" syntax. For example, if you wanted to import lodash into your TypeScript application, you might use the following import statement:
import * as _ from "lodash";
I think earlier versions of TypeScript allowed for a simplified default import syntax; but, I believe that the TypeScript team dropped it in favor of the more explicit syntax. Over the weekend, however, I was reading through Basarat Ali Syed's book, "TypeScript Deep Dive", when I saw him use a very simple "import =" expression that I had not seen before. Though somewhat inconsistent with the rest of my import statements, the simplified syntax makes it quite nice. And, something I thought would be worth sharing.
Run this demo in my JavaScript Demos project on GitHub.
Ironically, the "import =" syntax is right there in the Modules portion of the TypeScript documentation. However, I have yet to sit down and actually "read the manual" on TypeScript. So, it was just lucky that I discovered it in Bararat's book:
import _ = require( "lodash" );
It seems a little unusual to mash together the ES6 module syntax with the CommonJS module syntax. But, it's still better than the "* as" syntax mentioned above.
To see this "import = require" syntax in action, I put together a small Angular 2 demo in which I import lodash and use it to map one array of values onto another array of values:
// Import the core angular services.
// --
// NOTE: When I'm including the LODASH library, I'm using the "import =" syntax since
// the lodash library has a single top-level export.
import _ = require( "lodash" );
import { Component } from "@angular/core";
interface Friend {
id: number;
name: string;
}
@Component({
selector: "my-app",
styleUrls: [ "./app.component.css" ],
template:
`
<p>
<strong>Friends:</strong> {{ names | json }}
</p>
`
})
export class AppComponent {
public friends: Friend[];
public names: string[];
// I initialize the app component.
constructor() {
this.friends = [
{ id: 1, name: "Kim" },
{ id: 2, name: "Sarah" },
{ id: 3, name: "Joanna" },
{ id: 4, name: "Libby" }
];
this.names = this.pluckNames( this.friends );
}
// ---
// PRIVATE METHODS.
// ---
// I return the names property as an array, plucked from the given collection.
private pluckNames( collection: Friend[] ) : string[] {
// NOTE: I need to explicitly cast the return value here because lodash
// overloads the .map() method instead of having an explicit "pluck" method.
// As such, the definition file gets confused on the return type.
return( <string[]>_.map( collection, "name" ) );
}
}
The example is silly; but, the TypeScript compiler executes without any error. And, when we run the above code, we get the following output:
As you can see, this "import = require" syntax works and, is in my opinion, much nicer than the "* as" syntax. Even though it does break the consistent form of the other import statements.
Now, since I've started using Webpack to build my Angular 2 demos, I've started creating a "vendors" file for things like lodash, so that Webpack's CommonsChunkPlugin plugin can properly isolate code that changes at a different rate. For this demo, my "main.vendor.ts" file looks like this:
// Import these libraries for their side-effects.
// --
// CAUTION: As you add more "import" statements to your application code, you will have
// to come back to this file and add those imports here as well (otherwise that imported
// content may get bundled with your main application bundle, not your vendor bundle.
import "@angular/core";
import "@angular/platform-browser-dynamic";
import "lodash";
As you can see, I am importing lodash using the TypeScript "side effect" syntax. This is compatible with the aforementioned "import = require" syntax and Webpack is able to successfully siphon the lodash library off into the vendor file.
I personally find this syntax, though not without its drawbacks, to be more pleasant than the "* as" syntax that I've been using up until now. You can technically use this syntax with TypeScript files as well (if they use the "export =" syntax); however, I see this primarily as a way to consume non-TypeScript files, like lodash, in a TypeScript application.
NOTE: You still need to provide type definition files for your non-TypeScript modules; or, TypeScript will complain that they have an implicit type of "any".
Want to use code from this post? Check out the license.
Reader Comments
There is a typescript compiler option called allowSyntheticDefaultImports which allows you to import without the strange * syntax.
Though I'm not sure if it's a best practice to use it.
@Leon,
Do you know, I've tried to use that several times, both when I was working with System.js and I think when i was using the TypeScript compiler and I could never get it to work. The default export was always coming back as undefined. If you could point me towards a working example anywhere, I would be much appreciated!
@Ben
I made a quick demo repo for you
https://github.com/leon/demo-ng-lodash
Hmm... I personally prefer `import * as _ from "lodash"`. Since more consistent with the TypeScript default one, rather than CommonJs like. Thank you for showing the alternative though and thank you for keeping the blog post short.
I get ERROR in /Projects/shopping-list/src/app/app.component.ts (5,1): Import assignment cannot be used when targeting ECMAScript 2015 modules. Consider using 'import * as ns from
"mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
@Leon,
Very cool - I will see if I can get this running locally!
@Demisx,
Yeah, I definitely have an emotional problem with it being a different syntax. Seems, somehow, dirty. But, I also find the "* as foo" to be different than my other quasi-destructuring statements... so, to some degree, it's 6 of one, half-a-dozen of the other. I'm still trying to working around in my head.
@Jesse,
What version of TypeScript are you using? It's possible that the "require()" syntax was added later. I am not entirely sure of its history.
@Leon,
Interesting - I was able to download and run your demo successfully. But, when I try to do it in my own demo:
https://github.com/bennadel/JavaScript-Demos/tree/master/demos/import-require-angular2
... it still doesn't work. Without the "allowSyntheticDefaultImports" flag, the tsc CLI gives me the following error:
> error TS1192: Module '"/Users/ben/Sites/bennadel.com/projects/javascript_demos/vendor/angular2/2.4.9-webpack/node_modules/@types/lodash/index"' has no default export.
... and if I add the "allowSyntheticDefaultImports" flag, the tsc CLI doesn't complain, but the import "_" value is undefined in the app runtime.
There must be some fundamental difference between our two configurations / compilers; but, I am not knowledgeable enough to see it (mostly I know enough about TypeScript and the tsc compiler to just "get it working" and not much more than that).
It looks like this flag may also be failing in ts-node (which I can confirm):
https://github.com/TypeStrong/ts-node/issues/86
Ah, and from this ticket, it looks like this might not be a TypeScript issue at all, but a loader issue: https://github.com/Microsoft/TypeScript/issues/7518
> allowSyntheticDefaultImports does not change the output.
> All what allowSyntheticDefaultImports is tells the compiler that at
> runtime your loader (e.g. SystemJs) will perform this operation of
> mapping modules to default imports, and thus avoids the error at
> build time.
... so it could be that the my Webpack build is somehow not making it available? I'm super new to Webpack, so this is quite a bit over my head.