8

I'm trying to use Angular 1.x with TypeScript 1.5.3 and SystemJS. The index.html page is set up to System.import('bootstrapper') which should start things up.

bootstrapper.ts gets compiled to bootstrapper.js and works fine as long as it doesn't use angular (i.e. doing just a console.log() works ok)

Now, I'd like to import and use angular to bootstrap it. I've already done jspm install angular and I also installed some typings for angular using tsd. The typings are referenced at the top of the bootstrap.ts file.

Unfortunately doing import angular from 'angular' doesn't compile, I get Module "angular" has no default export. My questions are:

  1. Why doesn't import angular from 'angular' compile? Looking in the angular.d.ts file I see declare module 'angular'{ export = angular; } which, if I understand correctly, is a default export from the module angular of a variable (defined above in the typings file) declare var angular: angular.IAngularStatic
  2. I noticed that doing import 'angular' compiles and then I can actually reference angular and do e.g. angular.module(...), but I don't think I understand correctly how this works. Shouldn't import 'angular' do a "bare import", i.e. running a module only for its side effects? If that's the case, does that mean that this import actually registers angular in the global scope?

I'm pretty sure I don't understand correctly how modules/type definition files work in Typescript, thanks in advance for an explanation.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
adrian h.
  • 2,996
  • 3
  • 18
  • 24

2 Answers2

7

Firstly, the following is my preferred way to use AngularJS 1.5 with TypeScript and SystemJS:

index.html

<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>

<script>
  SystemJS.import('app')
    .then(function (app) {
      app.bootstrap(document);
    })
    .catch(function (reason) { 
      console.error(reason);
    });
</script>

app/main.ts

import angular from 'angular';

const app = angular.module('app', []);

export function bootstrap(target: Element | Document) {
  angular.bootstrap(target, [app.name], { strictDi: true });
}

tsconfig.json

{
    "compilerOptions": {
      "module": "system",
      "allowSyntheticDefaultImports": true,
      ....
    }
}

config.js (loader config, simplified)

SystemJS.config({
  transpiler: "typescript",
  packages: {
    "app": {
      "main": "main.ts",
      "defaultExtension": "ts",
      "meta": {
        "*.ts": {
          "loader": "typescript"
        }
      }
    }
  }
});

Notes:

  1. If you are using JSPM 0.17 specify "plugin-typescript", not "typescript" for the loader and transpiler or just run $ jspm init and select a transpiler.
  2. The you can import angular as a default, but it will still register itself on the global scope.
  3. The reason you are getting syntax errors is because angular, and its angular.d.ts declaration file contains a CommonJS style export = declaration to allow for module and global namespace based usage. SystemJS, for maximum compatibility, interpolates this into a default export at runtime.
  4. The TypeScript compiler needs to be made aware of this. This can be done by setting "module": "system", and/or specifying "allowSyntheticDefaultImports": true. I have done both for the sake of exposition and explicitness.
  5. If you aren't using jspm, you just need to tell system JS where to find the typescript using map or package config

    SystemJS.config({
      map: {
        "typescript": "node_modules/typescript"
      }
    });
    
Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
5

[...] if I understand correctly, is a default export from the module angular of a variable

Nope, that's not what's happening. Angular exports the entire namespace as the export, if that makes sense.

import angular from 'angular' is attempting to import the default from the module.

What you want is import * as angular from 'angular'; which imports the entire export as a variable.

thoughtrepo
  • 8,306
  • 2
  • 27
  • 21
  • 2
    Just to note, `import angular` does not try to import something named `angular`, it tried to import something named `default` into a binding called `angular`. – loganfsmyth Sep 01 '15 at 17:54
  • @loganfsmyth Thanks, got it mixed up with the braces, thanks – thoughtrepo Sep 01 '15 at 17:55
  • Not sure i get it. Are you saying that in `export = angular` the `angular` refers to the module defined next in the d.ts file as `define module angular` instead of the variabile declared above as `declare var angular: angular.IAngularStatic`? Or is it referring to the variable, but the variable being of an interface type it exports somehow that interface as a namespace? If this would be the case then I guess `declare var x` doesn't actually create a variable it's just an alias for `module.interface`? – adrian h. Sep 01 '15 at 19:36
  • `declare var x` does create/declare the variable, but that variable isn't exported in the module. An exported variable would look like `export declare var x;` or `export = { x: x };`. When the `--module` flag is set, the declared `angular` variable only exists within the module's scope. When the `--module` flag isn't set, the declared `angular` variable exists within the global scope. – thoughtrepo Sep 01 '15 at 19:53
  • Ok makes sense that the variable isn't exported directly since there is no `export`. So then what does the `angular` in the `export = angular` refer to? This module-local variable? Or the module declared below as `declare module angular`? – adrian h. Sep 01 '15 at 19:56
  • Confusing, but the declared `angular` variable and `angular` module are the same variable. The `angular` module is really only being used to hold all the interfaces. Things can be exported or `export` can be set to something, but `export` itself can't be defined. So at `export = angular` spot the `angular` variable is being used to define the `export` of the `"angular"` module by passing along the `angular.IAngularStatic` type. – thoughtrepo Sep 01 '15 at 20:23
  • I think this is the part that I don't get: "the declared `angular` variable and `angular` module are the same variable". How is this possible since the variable is of type `angular.IAngularStatic` (so, a type inside the `angular` module) and the module is...well...a module? :) How can they be the same? I would understand if the export would just export the type `angular.IAngularStatic`, but that doesn't seem to be the case, because after `import * as angular from 'angular'` I can `let x: foo.IServiceProvider` which is defined in the `angular` module, not in the `IAngularStatic` interface. – adrian h. Sep 02 '15 at 06:56
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88555/discussion-between-adrian-hara-and-mallison). – adrian h. Sep 02 '15 at 08:36
  • This is because of declaration merging. The namespace declaration and the variable declaration are actually merged into a single conceptual unit with different meanings in different places. Also, while `import * as angular from 'angular'` will work, `import * as $ from 'jquery'` will fail if $ is called, e.g. $("div"). You should enable `syntheticDefaultImports` in TypeScript and import using `import $ from 'jquery'` – Aluan Haddad May 30 '16 at 03:14