ProudMonkey

Upgrading from AngularJS 2 Beta to RC 6

Introduction

In the past months, I’ve demonstrated how to get started with Angular 2 within ASP.NET Core. The demo project were using Angular 2 Beta version because that’s the current release at that time of writing. Recently, Angular 2 Release Candidate 6 (RC6) version was released. To align with the latest version, I think it’s a good idea to upgrade to keep up with the most stable release and utilize Angular 2 features to their full extent.

You can find the current release change log here: CHANGELOG.md

Important: The current release of Angular 2 requires at least node v4.x.x and npm 3.x.x. Verify that you are running the versions mentioned, by running node -v and npm -v in a terminal/console window. Older versions produce errors.

Let’s Start Modifying!

Before making any file changes, make sure, that you have the supported versions of Node and NPM. If you have the older versions, uninstall node, download and install the required version here: Node.js

NPM is already integrated in the Node installer, so you don’t have to worry about manually upgrading it.

After the installation, make sure to restart your machine to ensure the updates will take effect. In my case, I have the following versions installed:

  • Node v6.3.1 (the current release as of this writing).
  • NPM 3.10.3 (latest version as of this writing).

Keep in mind that this upgrade is also applicable if you were using Angular 2 RC 4 versions and below.

Adding the typings.json File

The typings.json file might not be needed since the release of RC 5 and above. If there are cases we need to support backward compatibility and support previous RC versions then, we need to create a new file for creating typings containing node, jasmine and core-js typings. To do that, just right-click on your project root and then add a new .json file. In my case, I have selected the “NPM Config File” and just rename it as “typings.json”. Replace the default generated content with the following below:

{
  "globalDependencies": {
    "core-js": "registry:dt/core-js#0.0.0+20160602141332",
    "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
    "node": "registry:dt/node#6.0.0+20160621231320"
  }
}

Note: the core-js line in the typings.json file is the only required one, yet we’ve also took the chance to add the jasmine and node typings: you could need them in the near future, should you want to use the Jasmine test framework and/or use code that references objects in the nodejs environment. Keep them there for now, they won’t hurt your project.

Updating the package.json File

If you are not supporting previous versions of angular, I would really recommend you to delete the existing node_modules folder before updating the package.json file. This will ensure that we won’t be having a local copy of the previous angular 2 bundles and modules.

If you have decided, open the package.json file and replace everything in it with the following:

{
  "version": "1.0.0",
  "name": "Your Application Name",
  "author": "Optional Field",
  "description": "Optional Field",
  "dependencies": {
    "@angular/common": "2.0.0-rc.6",
    "@angular/compiler": "2.0.0-rc.6",
    "@angular/core": "2.0.0-rc.6",
    "@angular/http": "2.0.0-rc.6",
    "@angular/platform-browser": "2.0.0-rc.6",
    "@angular/platform-browser-dynamic": "2.0.0-rc.6",
    "@angular/upgrade": "2.0.0-rc.6",
    "@angular/forms": "2.0.0-rc.6",
    "@angular/router": "3.0.0-rc.2",

    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.6",
    "systemjs": "^0.19.37",
    "typings": "^1.3.2",
    "zone.js": "^0.6.12",
    "moment": "^2.14.1"
  },

  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-clean": "^0.3.2",
    "gulp-concat": "^2.6.0",
    "gulp-less": "^3.1.0",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-typescript": "^2.13.6",
    "gulp-uglify": "^2.0.0",
    "typescript": "^1.8.10"
  },
  "scripts": {
    "postinstall": "typings install dt~core-js --global"
  }
}

Note: On 9/14/2016, Angular 2 Final release was out. If you'd like to use the final release version, then the upgrade process from RC6 to Final is pretty easy; We just need to remove the -rc.x suffix from the package.json references.

Here's a quick example using the Final release version:

"@angular/common": "2.0.0",
"@angular/compiler": "2.0.0",
"@angular/core": "2.0.0",
"@angular/forms": "2.0.0",
"@angular/http": "2.0.0",
"@angular/platform-browser": "2.0.0",
"@angular/platform-browser-dynamic": "2.0.0",
"@angular/router": "3.0.0",
"@angular/upgrade": "2.0.0",

The packages with the @ symbol is part of the new Angular 2 bundle: the other ones are loading libraries, helper tools and polyfills for older browser support. The current version of Angular 2 as of this writing is 2.0.0-rc.6. You can check for the updates here. You may also have noticed that we’ve replaced the dependencies to Grunt with Gulp ~ I’ll explain why later in this article. You may remove the existing gruntfile.js file just like what I did if you’d like, but if you prefer using Grunt, feel free do so. No one is going to fire you of using Grunt :)

If you may have noticed, we’ve added the scripts section to install the typings. Typings manages and install the TypeScript definitions.
Now, save the file to restore the needed packages for our application.

Once installed, you should be able to see something like this:

Tip: If Typings didn’t install successfully on Save, the try to use the "Restore Package" option by right-clicking on the Dependencies node. Another way is to use the command line to run typings explicitly. To do that, just navigate to the root folder of your app and do a CTRL+SHIFT, then select "Open command window here". In the command line, type in the following command:

npm run typings install

If successful, you should be able to see something like this:

As a recap, all Angular 2 dependencies will be installed in the following location in your local drive:

../src/<YourProjectName>/node_modules

Updating the tsconfig.json File

Now open your tsconfig.json file, if you do not have it, you need to create one. Here’s the updated TypeScript JSON Configuration file:

{
  "compileOnSave": false,
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "system",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noEmitOnError": false,
    "removeComments": false,
    "target": "es5"
  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

The compileOnSave signals the IDE to generate all files for a given tsconfig.json upon saving. compilerOptions configuration will influence how the Intellisense, and our external TypeScript compiler will work. By excluding the folders in the config, we tell the built-in TypeScript compiler provided by Visual Studio 2015 to disable compiling the external TypeScript files within that location.

Updating the AppComponent File

Since we are upgrading to RC version, we also need to update the component that have references to previous beta version of Angular. In our particular example, we need to update our app.component.ts file to this:

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

@Component({
    selector: 'angularjs2demo',
    template: `<h1>AngularJS 2 Demo</h1><div>Hello ASP.NET Core! Greetings from AngularJS 2.</div>`
})

export class AppComponent { }  

There’s not much to say about the update above. We just changed the import reference to the new name which is @angular/core.

The Angular 2 Module File

Angular Modules, also known as NgModules, have been introduced since Angular2 RC5. This provides a powerful way to organize and bootstrap any Angular2 application: they help developers to consolidate their own set of components, directives and pipes into reusable blocks.

Every Angular2 application since RC5 must have at least one module, which is conventionally called root module and given the AppModule class name.

Now, create a new TypeScript file and name the file as "app.module.ts". If you are following my previous article here, then you may create the file under Scripts/app folder. Here’s how the app.module.ts file should look like:

///<reference path="../../typings/index.d.ts"/>
import {NgModule} from "@angular/core";  
import {BrowserModule} from "@angular/platform-browser";  
import {HttpModule} from "@angular/http";  
import "rxjs/Rx";

import {AppComponent} from "./app.component";

@NgModule({
    // directives, components, and pipes
    declarations: [
        AppComponent
    ],
    // modules
    imports: [
        BrowserModule,
        HttpModule
    ],
    // providers
    providers: [
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { }  

The first line from the config above, adds a reference to the type definitions to ensure our TypeScript compiler will find it. Note that, in case we’re using a CDN or a pre-compiled version of Angular2, we could (and should) definitely remove this line. We then import the basic Angular2 modules that we will need for our app. You can add more Angular 2 module reference in this file when needed.We have also imported the rxjs library definition file(s), which will be useful to compile some Angular2 libraries. We then imported our component "AppComponent". Finally, we have declared our root NgModule: as we can see it consists in an array of named arrays, each one containing a set of Angular2 objects that serves a common purpose: directives, components, pipes, modules and providers. The last one of them contains the component(s) we want to bootstrap, which in our case, the AppComponent file.

For more information about App Module, see: https://angular.io/docs/ts/latest/cookbook/rc4-to-rc5.html

Updating the Boot File

Open boot.ts file and replace the existing code with the following:

import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";  
import {AppModule} from "./app.module";

platformBrowserDynamic().bootstrapModule(AppModule);  

Just like in the app.component.ts file, we changed the reference to the new Angular bundle. Notice that we have referenced the new AppModule that we have created previously. Now we’re just missing an entry point to load with the browser: let’s add it right now.

Adding the SystemJS File

Now, let’s add the systemjs configuration file. Right-click on the wwwroot folder and then select Add > New Item. Under Client-side templates, select "JavaScript File" just like in the figure shown below:

Name the file as "systemjs.config.js" and then copy the following code below:

(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': 'js/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',

            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

            // angular testing umd bundles
            '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
            '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
            '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
            '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
            '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
            '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
            '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
            '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',

            // other libraries
            'rxjs': 'npm:rxjs',
            'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './boot.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            },
            'angular2-in-memory-web-api': {
                defaultExtension: 'js'
            }
        }
    });
})(this);

The SystemJS.config file will load the application and library modules. We are going to need this loader to build our Angular 2 app. For more details about SystemJS config, read on: SystemJS
If you want to use WebPack as your module bundler, see: Introduction to WebPack

Updating the index.html

Now, since we need the SystemJS config to load our app modules and components, then we need to update our index.html to include the new configuration. Replace your index.html file so it will look something like this:

<html>  
<head>  
    <base href="/">
    <title>ASP.NET Core with Angular 2 RC Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Step 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="js/shim.min.js"></script>
    <script src="js/zone.js"></script>
    <script src="js/Reflect.js"></script>
    <script src="js/system.src.js"></script>

    <!-- Angular2 Native Directives -->
    <script src="/js/moment.js"></script>

    <!-- Step 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>  
<!-- Step 3. Display the application -->  
<body>  
    <!-- Application PlaceHolder -->
    <angularjs2demo>Please wait...</angularjs2demo>
</body>  
</html>  

Switching to Gulp

This time, we will be using Gulp as the JavaScript Task Runner to automate our client-side scripts. I’d like to note that switching to Gulp does not mean Grunt is a bad tool. Of course Grunt is still a great tool and used by many. If you prefer, you can always use it to automate tasks for you. I just preferred to use Gulp because of its conciseness and easy task writing. It also uses node.js’ streams, and executes faster, since it does not open/close files, or creates intermediary copies all the time. But then again, every developer has preference, so it’s all up to you which JS task runners you would use :).

Now let’s add a new file for our Gulp configuration. Go ahead and right click on the project solution and then select Add > New Item. Under Client-side template, select "Gulp Configuration File" as shown in the figure below:

Click Add to generate the file, and then replace the default generated configuration with the following code below:

var gulp = require('gulp'),  
    gp_clean = require('gulp-clean'),
    gp_concat = require('gulp-concat'),
    gp_less = require('gulp-less'),
    gp_sourcemaps = require('gulp-sourcemaps'),
    gp_typescript = require('gulp-typescript'),
    gp_uglify = require('gulp-uglify');

/// Define paths
var srcPaths = {  
    app: ['Scripts/app/main.ts', 'Scripts/app/**/*.ts'],
    js: [
        'Scripts/js/**/*.js',
        'node_modules/core-js/client/shim.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system.src.js',
        'node_modules/typescript/lib/typescript.js',
        'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js',
        'node_modules/moment/moment.js'
    ],
    js_angular: [
        'node_modules/@angular/**'
    ],
    js_rxjs: [
        'node_modules/rxjs/**'
    ]
};

var destPaths = {  
    app: 'wwwroot/app/',
    js: 'wwwroot/js/',
    js_angular: 'wwwroot/js/@angular/',
    js_rxjs: 'wwwroot/js/rxjs/'
};

// Compile, minify and create sourcemaps all TypeScript files 
// and place them to wwwroot/app, together with their js.map files.
gulp.task('app', ['app_clean'], function () {  
    return gulp.src(srcPaths.app)
        .pipe(gp_sourcemaps.init())
        .pipe(gp_typescript(require('./tsconfig.json').compilerOptions))
        .pipe(gp_uglify({ mangle: false }))
        .pipe(gp_sourcemaps.write('/'))
        .pipe(gulp.dest(destPaths.app));
});

// Delete wwwroot/app contents
gulp.task('app_clean', function () {  
    return gulp.src(destPaths.app + "*", { read: false })
    .pipe(gp_clean({ force: true }));
});

// Copy all JS files from external libraries to wwwroot/js
gulp.task('js', function () {  
    gulp.src(srcPaths.js_angular)
        .pipe(gulp.dest(destPaths.js_angular));
    gulp.src(srcPaths.js_rxjs)
        .pipe(gulp.dest(destPaths.js_rxjs));
    return gulp.src(srcPaths.js)
        .pipe(gulp.dest(destPaths.js));
});


// Watch specified files and define what to do upon file changes
gulp.task('watch', function () {  
    gulp.watch([srcPaths.app, srcPaths.js], ['app', 'js']);
});

// Define the default task so it will launch all other tasks
gulp.task('default', ['app', 'js', 'watch']);  

The code above contains three (3) main variables:
gulp - initializes each Gulp plugins that we are going to need for running the tasks.

  • srcPaths - holds an array of sources that we want to copy and transpile.
  • destPaths - holds an array of specific location within wwwroot. This is basically where we dump the scripts we defined from the srcPaths.

It also contains five (5) main tasks:

  • app_clean - This task deletes the existing files from the destination folders we defined.
  • app - This task compiles, uglify and create sourcemaps for all TypeScript files and place them to wwwroot/app folder, together with their js.map files.
  • js - This task will copy all JavaScript files from external libraries which is located within the node_modules folder and place them to wwwroot/js folder.
  • watch - This task watches files defined in app and js tasks that are changed.
  • default - Define the default task so it will launch all other tasks

Testing the App

Clean and Build your solution and make sure it has no errors. If it builds successfully, then right click on the gulpfile.js and select "Task Runner Explorer"

Make sure to click the "Refresh" button to load the task as shown in the figure below:

Now, right-click to the default task and hit Run. You should be able to see something like this when successful.

After successfully running the Task Runner, you should also see that the "app" and "js" folders are generated within your "wwwroot" folder as shown in the figure below:

Running the application should result to something like this:

That’s it! I hope someone find this post useful.