56

I am a complete beginner to Angular and Angular2. I am confused about how the workflow is structured. I have been looking at the sample project that is present in the Angular2 site.

Correct me if I am wrong but what I know till now is that all the typescript is transpiled into javascript by the typescript compiler. Then the compiled javascript is actually what runs in the browser.

Now if I am importing the javascript files into typescript using ES6 import statements like the following-:

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

Why do I again need to use SystemJS to load them -:

map: {
      // our app is within the app folder
      app: 'app',
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',

I mean isn't that counter productive ? Looking at the transpiled javascript of the ts files it shows all import statements are converted into require() statements. First of all how does require() work in a ES5 js file, and second if thats the case what is SystemJS doing.

This really confusing me. Any help will be greatly appreciated.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
ng.newbie
  • 2,807
  • 3
  • 23
  • 57
  • AFAIK, the browsers don't support ES6 yet. So SystemJS helps with it. Anyways wait for a detailed answer by someone else. – v1shnu Nov 18 '16 at 05:28

1 Answers1

116

When tsc compiles typescript into JavaScript, you end up with a bunch of js files on your local system. They somehow need to be loaded into a browser. Since browsers don't support native ES6 module loading yet, you have two options, either put them all into your index.html file in the correct order of dependencies, or you can use a loader to do that all for you. You specify the root for all modules, and then all files are loaded and executed by that loader in the correct order of dependencies. There are many loaders: requirejs, webpack, systemjs and others. In your particular case it's systemjs.

Looking at the transpiled javascript of the ts files it shows all import statements are converted into require() statements.

Yes, this is a way for SystemJs to load bundles. It uses require() and exports syntax because that's the CommonJS syntax for loading bundles and you specified this type in your tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",

If you were to put module:'es6', you would see that in your compiled javascript files the import and export statements are preserved. However, as mentioned before, you still can't use this syntax as browsers don't support it natively. If you were to put module:'amd', you would see different syntax that uses define(). I guess the systemjs loader is preferred in angular2 starter tutorial since it actually can load all module types supported by tsc. However, if you want to load modules as es6 modules, you have to put module: 'system' in your tsconfig.json. It's a module system designed to adhere to es6 modules standard and used until there's a full support of es6 modules in browsers.

How the setup works

In your index.html you add the following script:

<script>
    System.import('app').catch(function (err) {
        console.error(err);
    });
</script>

which is executed when index.html is loaded. The import('app') method instructs systemjs to load app module which is mapped to app folder in your project directory structure as specified by the configuration in systemjs.config.js:

map: {
    // our app is within the app folder
    app: 'app',

SystemJs looks for main.js file in that folder. When app/main.js is found and loaded into a browser, inside it's code the call of require is found:

var app_module_1 = require('./app.module');

and systemjs then fetches app.module.js file from local system. This one in turn has its own dependcies, like:

var core_1 = require('@angular/core');

And the cycle repeats - load, search for dependencie, load them and execute. And this is the way all dependecies are resolved, loaded and executed in a browser by the systemjs.

Why the mappings to core @angular libraries are required

In the systemjs.config.ts file there are mapping to the core @angular modules:

map: {
  ...
  // angular bundles
  '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
  '@angular/common': 'npm:@angular/common/bundles/common.umd.js',

The first thing to understand here is that these are mappings, not dependencies. It means that if none of your files import @angular/core, it will not be loaded to a browser. However, you may see that this particular module is imported inside app/app.module.ts:

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

Now, why the mappings are there. Suppose systemjs loaded your app/app.module.js into the browser. It parses its content and finds the following:

var core_1 = require('@angular/core');

Now systemjs understands that it needs to resolve and load @angular/core. It first goes through the process of checking mappings, as specified in the docs:

The map option is similar to paths, but acts very early in the normalization process. It allows you to map a module alias to a location or package.

I would call it a resolution by a named module. So, it finds the mapping and substitutes @angular/core with node_modules/@angular/core and this is where the real files are placed.

I think systemjs tries to imitate the approach used in node.js where you can specify a module without relative path identifiers ['/', '../', or './'], simply like this require('bar.js') and node.js:

then Node.js starts at the parent directory of the current module, and adds /node_modules, and attempts to load the module from that location.

If you wanted, you could avoid using named mappings and import by using relative path like this:

import {NgModule} from '../node_modules/@angular/core';

However, this should be done in all references to @angular.core in the project and lib files, including @angular, which is not a good solution to say the least.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • @Maximus Then why are we specifying Angular2 dependencies separately in the `system.config.js`. Tell me something, the `app_module` file has all the dependencies for Angular2 so why can't we just load that and forget about mentioning the dependencies separately. The @angular/http and rxjs dependencies are specified separately, even though the http module is based on rxjs. – ng.newbie Nov 18 '16 at 07:19
  • _Then why are we specifying Angular2 dependencies separately in the system.config.js_ - show the code where. If you mean that `'rxjs': 'npm:rxjs'`, it's not a dependency entry, it's a mapping entry – Max Koretskyi Nov 18 '16 at 07:55
  • @Maximus [link](https://angular.io/docs/ts/latest/quickstart.html) go to the `system.config.js` tab. You will see all the angular2 entries are mapped out. **Why does it need to be mapped out ?** Why I am asking is because during the typescript compilation all the files are converted to modules that can be used SystemJS(no transpile needed here AFAIK), so if I have to add the absolute path of the dependencies I need to know all the nested dependencies and all the locations of that. For example I need to know `@angular/http` depends on `rxjs` and I need to provide the mapping for both of them. – ng.newbie Nov 18 '16 at 08:35
  • @ng.newbie, updated my answer. you're right, you need to specify the mappings for all dependencies. If you don't, a browser will to tell you about that - `404 for http://localhost:3000/rxjs/Observable.js` if you comment `'rxjs': 'npm:rxjs',` in the `systemjs.config.js` – Max Koretskyi Nov 18 '16 at 09:06
  • No just disappointed that SystemJS cannot map transitive dependencies automatically. You I mainly come from a Java background, dependency tools like Maven download all dependencies related to a particular user defined dependency automatically. Like if I download Hibernate from Maven all dependencies of Hibernate will get downloaded automatically. Just wish Angular used a module loader like that. Will be posting a question about this soon. – ng.newbie Nov 19 '16 at 11:36
  • ok, you can accept my answer if it answers your question. while maven is a "native" build system, systemjs or other loaders are "substitutes" module loading until native loaders - browsers - will implement the functionality. – Max Koretskyi Nov 19 '16 at 12:39
  • @ng.newbie you can try [jspm](http://jspm.io/) which is a package manager built on top of SystemJS and tries to do just what you described here. – artem Nov 20 '16 at 02:39
  • 4
    Thank you for taking the time to explain this in detail. It's hard to find this information anywhere else. – Tyguy7 Jan 13 '17 at 22:38
  • @Tyguy, glad it helped – Max Koretskyi Jan 14 '17 at 07:14
  • 2
    What a great read. I was struggling to understand how the entire ecosystem works what happens under the hood. – Sangram Nandkhile Aug 23 '17 at 19:26
  • 2
    @Sangram, thanks, you can follow [angularindepth.com](https://blog.angularindepth.com/) publication for more in-depth topics you won't find anywhere else – Max Koretskyi Aug 23 '17 at 19:37
  • Can somehow specify where the SystemJS Config is actually at in an Angular project created with Angular CLI? It does not appear to have one by default and I'm trying to figure out the right place to add SystemJS configurations. – karns Mar 19 '18 at 15:47
  • 2
    @karns, systemjs is not used inside CLI in current versions. Webpack is the underlying build system – Max Koretskyi Mar 20 '18 at 15:11