June 19, 2016

Angular 2: Official tutorial projects in ES6 + TypeScript + seed! (part 2 of 3)


Pages: 1 2 3

How to set up an Angular 2 project: ECMAScript  6 / 7

Here I will explain how to setup an Angular 2 project from scratch. This is actually based on my experience setting up the tutorial projects described above. It may be helpful to refer to the tutorial project setup while reading through this chapter. By explaining the exact steps needed to create an Angular 2 project, I hope to “break its magic”, providing you with a deeper understanding of what is really going on under the hood.

Of course, reading this chapter is completely optional. You can just take the tutorial project as a skeleton and start writing your business code.

Note that this chapter is not a strict step-by-step tutorial. Project build / startup will probably fail unless all of below steps are completed.

Generate skeleton

We will start with a typical AngularJS project setup which I assume you are familiar with. You can e.g. use the Yeoman AngularJS generator to scaffold a simple AngularJS project:
yo angular

Project structure

Here I use this project structure:
  • Main folder: Contains all the project configuration files
  • app folder: Contains the actual business code
    • index.html: The Single Page Application umbrella HTML page
    • scripts folder: Contains the business code, written in ES6
    • pages folder: Contains the HTML pages (views)
    • styles folder: Contains the CSS files
  • node_modules. Generated by npm. Contains the npm dependencies.
  • dist
    • scripts: Generated by babel. Contains the transpiled ES5 JavaScript code.

npm dependencies

See the complete package.json file here.

The package.json file defines the project’s dependencies. We install all the tools we need into the local node_modules folder in order to become independent of the machine’s build tools setup.

Note some of these essential dependencies:
  • @angular…: As of Angular 2 RC-1, the Angular root package has changed from angular2 to @angular.
  • A few of Angular 2’s own runtime dependencies, as discussed later, including the SystemJS dependency management runtime library.
  • babel…: Babel transpiles ES6 to ES5 at build time. Note that we need a few additional plugins to enable some experimental features. We’ll discuss these later.
  • A few additional build time dependencies, mostly for Grunt (including the grunt-babel task).
Install the dependencies with
npm install

Grunt setup

See the complete Gruntfile.js file here.

Starting off with an AngularJS-like Grunt setup (as e.g. generated by running yo angular), we need to apply these changes to fit it for use with Angular 2:
  • Configure Babel which does the transpilation from ES6 to ES5 during the build:
    • Set it up in initConfig(). Note that we provide additonal options through the .babelrc file (see below).
    • Register it for running during server startup (run / default)
    • Register it for running during server live reload (watch)
  • Expose the appropriate file paths to the web (serveStatic).
    • Most importantly, we will expose dist/scripts (the transpiled scripts), systemjs.config.js (the SystemJS config, see below), and node_modules (as imposed by usage of SystemJS, see below).
  • Angular 2 by default uses “HTML 5 mode” for routing, i.e. instead of using <a> anchors (# in the URL), an actual redirect / UL redirect is faked. In order for that to work, we must redirect all requets on the server side. This is achieved by using the 'connect-modrewrite' plugin for Grunt.
If you feel more comfortable with an alternative build tool, e.g. Gulp, you can use it instead.

Transpilation config

See the complete babel.rc file here.

As mentioned earlier, we use Babel to transpile ES6 code to ES5 such that the browser can read it.

Note that in the example project setup, transpilation happens as an additional build step which is for many reasons to be preferred over transpiling on-the-fly in the browser. We need to transpile our own code only, not external modules (node_modules) as these typically come in plain old ES5 already through npm.

We set up Babel in the build configuration file. Additional configuration goes into the babel.rc file which is implicitly applied:
{
  "plugins": [
    "angular2-annotations",
    "transform-decorators-legacy",
    "transform-class-properties",
    "transform-flow-strip-types"
  ],
  "presets": [
    "es2015"
  ]
}
  • First and foremost, we need to specify the main "es2015" preset which will apply all the necessary transformations to turn ES6 code into ES5.
However, in order to make full use of Angular 2’s syntax, we need to enable support for some additional experimental syntactical elements:
  • Angular 2 makes heavy use of so-called Decorators (e.g. @Component({…})) which, although part of TypeScript, are a mere experimental proposal for future ECMAScript (we’re even talking about ES6’s successor ES7 here). Although we could write Angular 2 code without them, they make the syntax much cleaner. Thus we enable them with the 3rd party "transform-decorators-legacy" plugin.
  • Angular 2 ‘s dependency injection logic is based on typed constructor parameters where the type (a.k.a. class) of a parameter is used to resolve a dependency. In ES6 (and ES7), there is no notion of a type system. Thus comes the "angular2-annotations" plugin, which fakes typed parameters.
  • Conveniently, "angular2-annotations" also enables us to specify fields on ES6 classes which is not supported by native syntax.
Note that although these enhancements make it more easy to write concise Angular 2 syntax, we are technically not writing standard-compliant ES6 code any more now.

There may be similar configuration steps for alternative transpilers such as Traceur.

SystemJS runtime module dependency config

See the complete systemjs.config.js file here.

As in the official examples, we will use SystemJS as a module dependency management system here. It will resolve module dependencies as requested by import / export statements.

Note that in this configuration, Babel ES6 to ES5 transformation runs first, and module dependencies are actually resolved in the resulting ES5 files at runtime.

SystemJS configuration happens in the systemjs.config.js file. Here, SystemJS is instructed how to link between package names and the respective JavaScript library.

Unless you need 3rd party libraries, you can typically just reuse the example config file for any of your AngularJS projects.

If you don’t like SystemJS, you can use a browser dependency management tool of your choice instead, e.g. Bower or Webpack.

index.html

See the complete index.html file here.

This is the entry page of an Angular 2 single page application. Because all content is dynamically rendered in this page, it is typically the only place where we need to define the HTML <header>.

These are the essential parts for a working Angular 2 application:
  • <base href="/"> is required for routing / navigation (@RouteConfig) in “HTML 5 mode”.
  • A number of 3rd party scripts need to be imported. These are described in the Angular 2 documentation. To sum up:
    • zone.js: A runtime dependency for asynchronous computation
    • rxjs: Another runtime dependency for asynchronous computation
    • systemjs: The SystemJS runtime dependency (see above)
  • Then, there are the two local SystemJS scripts:
    • The systemjs.config.js config file import (see above)
    • The actual execution of importing the main application module through SystemJS (see below) as an inline script
  • In the <body>, we simply insert the selector of our main component (see below), plus a placeholder to be shown during application startup.

JavaScript runtime bootstrapping

See the complete main.js file here.

In index.html, the SystemJS import of the main module
System.import('app')
is resolved within systemjs.config.js to load the main.js file
'app': { main: 'main.js',  defaultExtension: 'js' },
which does the actual bootstrapping of the Angular 2 application:
bootstrap(AppComponent, [
    ...
]);
This refers to the application’s main component (see blow) and injects dependency providers on which it depends.

You can take the example main.js file as a template for your own applications, and adjust module imports / the dependency provider list accordingly.

The main app component

See the complete app.component.js file here.

AngularJS component files typically have this structure:
  • imports from other JavaScript files
  • Metadata decorators
    • In the case of a component, typically @Component({…})
    • In the case of the main component, typically also @RouteConfig([…]) for the router / navigation configuration.
    • In the case of a service, typically just @Injectable()
  • The actual component as an exported class
    • Thanks to the "angular2-annotations" Babel plugin (see above), we can define fields on a class.
    • If the component’s logic needs an external component / service, these must be explicitly defined and set in the constructor.
By convention, the main component should be named AppComponent and be registered to the my-app selector.

The latter is what renders this component in lieu of the <my-app> placeholder in index.html.

If your application needs routing (sub-pages), don’t forget to include <router-outlet></router-outlet> where you want the content of the current route to be rendered. This is typically placed in the main component’s HTML template.

Additional components

See the app/scripts directory here.

Even more than AngularJS, Angular 2 promotes a very modular programming model. You should try to follow this by keeping the main application component free of any business logic and implement the latter in separate, dedicated components and services, one per JavaScript file.

For the actual implementation part, you can follow Angular 2’s official TypeScript tutorials even if you use ECMAScript 6 / 7. Most of the syntax is nearly identical, except for a few differences:
  • Interfaces are not supported and actually useless (because there’s no static typing). Simply delete all implements… declarations.
  • The private keyword is not supported. Simply delete it.
  • As a consequence of the above: The more concise TypeScript constructor syntax constructor(private http: Http) { } is not supported. You have to explicitly do constructor(http: Http) { this.http = http; } instead.

Server startup

See the complete package.json file here.

The package.json file specifies the start script:
"start": "grunt serve"
Thus, start the server with
npm start
Pages: 1 2 3