Search code examples
javascriptangularsystemjsangular-cli

Import Underscore to Angular 2 CLI project


I've started a new project with the Angular CLI tool. After that I follow this official guide to import Underscore and I do exactly as it says.

But still my project crashes in the browser when I try to use Underscore in my app.component with the error message:

ORIGINAL EXCEPTION: ReferenceError: _ is not defined

Underscore is added to the dist/vendor folder so my guess would be that the problem is in the Systemjs configuration.

Here is my angular-cli-build:

var Angular2App = require('angular-cli/lib/broccoli/angular2-app');

module.exports = function(defaults) {
  return new Angular2App(defaults, {
    sassCompiler: {
      includePaths: [
        'src/styles'
      ]
    },
    vendorNpmFiles: [
      'systemjs/dist/system-polyfills.js',
      'systemjs/dist/system.src.js',
      'zone.js/dist/**/*.+(js|js.map)',
      'es6-shim/es6-shim.js',
      'reflect-metadata/**/*.+(ts|js|js.map)',
      'rxjs/**/*.+(js|js.map)',
      'underscore/underscore.js',
      '@angular/**/*.+(js|js.map)'
    ]
  });
};

Here is my system-config:

"use strict";

// SystemJS configuration file, see links for more information
// https://github.com/systemjs/systemjs
// https://github.com/systemjs/systemjs/blob/master/docs/config-api.md

/***********************************************************************************************
 * User Configuration.
 **********************************************************************************************/
/** Map relative paths to URLs. */
const map: any = {
  'underscore':                 'vendor/underscore/',
};

/** User packages configuration. */
const packages: any = {
  'underscore':                 { main: 'underscore.js', format: 'cjs' },
};

////////////////////////////////////////////////////////////////////////////////////////////////
/***********************************************************************************************
 * Everything underneath this line is managed by the CLI.
 **********************************************************************************************/
const barrels: string[] = [
  // Angular specific barrels.
  '@angular/core',
  '@angular/common',
  '@angular/compiler',
  '@angular/forms',
  '@angular/http',
  '@angular/router',
  '@angular/platform-browser',
  '@angular/platform-browser-dynamic',

  // Thirdparty barrels.
  'rxjs',

  // App specific barrels.
  'app',
  'app/shared',
  /** @cli-barrel */
];

const cliSystemConfigPackages: any = {};
barrels.forEach((barrelName: string) => {
  cliSystemConfigPackages[barrelName] = { main: 'index' };
});

/** Type declaration for ambient System. */
declare var System: any;

// Apply the CLI SystemJS configuration.
System.config({
  map: {
    '@angular': 'vendor/@angular',
    'rxjs': 'vendor/rxjs',
    'main': 'main.js'
  },
  packages: cliSystemConfigPackages
});

// Apply the user's configuration.
System.config({ map, packages });

My app.component:

/// <reference path="../../typings/globals/underscore/index.d.ts" />

import { Component } from '@angular/core';

declare var _;

@Component({
  moduleId: module.id,
  selector: 'app-root',
  template: `<h1>{{title}}</h1>`
})
export class AppComponent {
  title = _.version;
}

Where do I go wrong?

And WHY is this so complicated? Will the community accept it being this cumbersome to just add a simple third party library?


Solution

  • Your configuration basically sets up underscore so SystemJS can find it when needed.

    When you changed system-config.ts, you told SystemJS: if anyone asks for underscore, it is a file underscore.js that can be found at the vendor/underscore/ folder -- and its module format is CommonJS (cjs).

    The changes at angular-cli-build.js are for telling angular-cli what files it should pick and throw into the vendor folder. (So, if you told SystemJS it'd find underscore there, this is what makes it be there.)

    But that alone does not import/add underscore into the global scope of your app.

    You have to import it at each .ts file so SystemJS adds it to the transpiled .js of that module.

    Instead of these two lines:

    /// <reference path="../../typings/globals/underscore/index.d.ts" />
    declare var _;
    

    Add this to your file:

    import * as _ from 'underscore';
    

    If you have problems, try to inspect the generated .js source being executed at the browser. In your case, you'll probably find that there is no require() method importing the underscore.