Sunday, August 7, 2016

Setting Up Angular 2 with Visual Studio 2015 - ASP.NET Core

In this post I will explain how to setup Angular2 with Visual Studio 2015 hosted inside ASP.NET Core project. Mostly the steps are taken from the Quick Start tutorial at https://angular.io.

We have to make sure that the following requisites are being installed.

  • Visual Studio 2015 - You can find community edition here. If you already have VS 2015 community edition installed, make sure that it has also installed Update3.
  • ASP.Net Core - You can find ASP.Net Core on the same link, it needs VS 2015 Update3.

Let start developing sample project. Create a new ASP.NET Core project.

Select empty template.

Open project.json file and add the static files support to serve an HTML page. For this, add following line in the dependencies node under the Kestrel dependency.

"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final"
"Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final"

So the final project.json file should be similar to this:

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final"
  },

  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
      "version": "1.0.0-preview1-final",
      "imports": "portable-net45+win8+dnxcore50"
    }
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "dnxcore50",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "gcServer": true
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

To host our Angular app, we will add an html page index.html under wwwroot folder.

To enable serving static files, like this html page, we have to modify the startup code. Open up Startup.cs file and remove any code in Configure method, and place this line:

app.UseStaticFiles();

At this stage, lets pause and test the work done till now, to see our index.html page running in browser. Follow these steps:

Open up VS Developer Command Prompt and navigate to the project folder, where web.config or project.json files are located. Type "dotnet run". App will be compiled and Kestrel web server will started.

Open browser and navigate to http://localhost:5000/index.html. You will see index.html page.

Try make changes in index.html and refresh the page in browser to see the changes reflected.

Now our html page is running in without Angular support. Lets proceed to next phase.

Press Ctrl+C on same command prompt to shutdown server.

Type "npm init" to initialize package.json file.

It will prompt for some inputs, you can provide any reasonable input, or just leave blank and press ENTER. And finally it will ask for "yes/no" option, then type "yes" and press ENTER.

Here your package.json file is initiated.

In Visual Studio - Solution Explorer you can see that "dependencies" node got a sub-folder npm. Open package.json file. Add "dependencies" and "devDependencies" nodes as below:

"dependencies": {
 "@angular/common": "2.0.0-rc.4",    
 "@angular/compiler": "2.0.0-rc.4",
 "@angular/core": "2.0.0-rc.4",
 "@angular/forms": "0.2.0",
 "@angular/http": "2.0.0-rc.4",
 "@angular/platform-browser": "2.0.0-rc.4",
 "@angular/platform-browser-dynamic": "2.0.0-rc.4",
 "@angular/router": "3.0.0-beta.1",
 "@angular/router-deprecated": "2.0.0-rc.2",
 "@angular/upgrade": "2.0.0-rc.4",
 "systemjs": "0.19.27",
 "core-js": "^2.4.0",
 "reflect-metadata": "^0.1.3",
 "rxjs": "5.0.0-beta.6",
 "zone.js": "^0.6.12",
 "angular2-in-memory-web-api": "0.0.14"  
},
  "devDependencies": {
    "typescript": "^1.8.10",
    "gulp": "^3.9.1",
    "path": "^0.12.7",
    "gulp-clean": "^0.3.2",
    "gulp-concat": "^2.6.0",
    "gulp-typescript": "^2.13.6",
    "typings": "^1.3.1",
    "gulp-tsc": "^1.2.0"
  },

So final package.json file should be similar to this:

{
  
"name": "webapplication11",
"version": "1.0.0",
"description": "",
"main": "index.js",

"dependencies": {
 "@angular/common": "2.0.0-rc.4",
 "@angular/compiler": "2.0.0-rc.4",
 "@angular/core": "2.0.0-rc.4",
 "@angular/forms": "0.2.0",
 "@angular/http": "2.0.0-rc.4",
 "@angular/platform-browser": "2.0.0-rc.4",
 "@angular/platform-browser-dynamic": "2.0.0-rc.4",
 "@angular/router": "3.0.0-beta.1",
 "@angular/router-deprecated": "2.0.0-rc.2",
 "@angular/upgrade": "2.0.0-rc.4",
 "systemjs": "0.19.27",
 "core-js": "^2.4.0",
 "reflect-metadata": "^0.1.3",
 "rxjs": "5.0.0-beta.6",
 "zone.js": "^0.6.12",
 "angular2-in-memory-web-api": "0.0.14"
},
  "devDependencies": {
    "typescript": "^1.8.10",
    "gulp": "^3.9.1",
    "path": "^0.12.7",
    "gulp-clean": "^0.3.2",
    "gulp-concat": "^2.6.0",
    "gulp-typescript": "^2.13.6",
    "typings": "^1.3.1",
    "gulp-tsc": "^1.2.0"
  },

  
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
 
"author": "",
"license": "ISC"

}

Add new item, with TypeScript JSON Configuration File item template at root of the project, with default file name(tsconfig.json). Add following lines in "compilerOptins" node under "target" node.

    "suppressImplicitAnyIndexErrors": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "system",
    "moduleResolution": "node"

And this line just above(outside) the compilerOptions node:

"compileOnSave": true,

Final tsconfig.json file should be similar to this:

{
  "compileOnSave": true,

  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5",
    "suppressImplicitAnyIndexErrors": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "system",
    "moduleResolution": "node"

  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

Add new item, with Gulp Configuration File item template at root of the project, with default file name(gulpfile.js). Update the file content with following:

var gulp = require('gulp');

gulp.task('copy', function () {
    gulp.src('./app/**/*.*')
        .pipe(gulp.dest('./wwwroot/app'));
});

gulp.task('watch', function () {
    gulp.watch('./app/**/*.*', ['copy']);
});

gulp.task('nodemodules', function () {
    gulp.src('./node_modules/core-js/**/*.js')
        .pipe(gulp.dest('./wwwroot/node_modules/core-js'));
    gulp.src('./node_modules/@angular/**/*.js')
        .pipe(gulp.dest('./wwwroot/node_modules/@angular'));
    gulp.src('./node_modules/zone.js/**/*.js')
        .pipe(gulp.dest('./wwwroot/node_modules/zone.js'));
    gulp.src('./node_modules/systemjs/**/*.js')
        .pipe(gulp.dest('./wwwroot/node_modules/systemjs'));
    gulp.src('./node_modules/reflect-metadata/**/*.js')
        .pipe(gulp.dest('./wwwroot/node_modules/reflect-metadata'));
    gulp.src('./node_modules/rxjs/**/*.js')
        .pipe(gulp.dest('./wwwroot/node_modules/rxjs'));

});

gulp.task('default', ['nodemodules', 'copy', 'watch']);

That's all things setup for Angular, now lets come the the real Angular part.

Create new folder called "app" under project root. Add new item main.ts to app folder, using TypeScript file template.

Place the following code in main.ts

import { bootstrap }    from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent);

Add new item app.component.ts to app folder, using TypeScript file template. Place the following code in app.component.ts.

import { Component } from '@angular/core';
@Component({
  selector: 'my-app',
  template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }

Create new JavaScript file startup.js under wwwroot folder, with following content.

/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
    // map tells the System loader where to look for things
    var map = {
        'app': 'app', // 'dist',
        '@angular': 'node_modules/@angular',
        'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
        'rxjs': 'node_modules/rxjs'
    };
    // packages tells the System loader how to load when no filename and/or no extension
    var packages = {
        'app': { main: 'main.js', defaultExtension: 'js' },
        'rxjs': { defaultExtension: 'js' },
        'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
    };
    var ngPackageNames = [
      'common',
      'compiler',
      'core',
      'http',
      'platform-browser',
      'platform-browser-dynamic',
      'router',
      'router-deprecated',
      'upgrade',
    ];
    // Individual files (~300 requests):
    function packIndex(pkgName) {
        packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
    }
    // Bundled (~40 requests):
    function packUmd(pkgName) {
        packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
    }
    // Most environments should use UMD; some (Karma) need the individual index files
    var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
    // Add package entries for angular packages
    ngPackageNames.forEach(setPackageConfig);
    var config = {
        map: map,
        packages: packages
    };
    System.config(config);
})(this);

Now, update index.html with the following content:

<html>
<head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    <!-- 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="startup.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>
<!-- 3. Display the application -->
<body>
    <my-app>Loading...</my-app>
</body>
</html>

And add new css file named styles.css in wwwroot folder with the following content taken from angular 5 min quickstart tutorial:

h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}
body {
  margin: 2em;
}
 /*
  * See https://github.com/angular/angular.io/blob/master/public/docs/_examples/styles.css
  * for the full set of master styles used by the documentation samples
  */

That's finished, try run your project and you will see output message:

My First Angular 2 App

You can download the sample project from my github repo Angular2-GettingStarted-With-VS2015. You may need to restore all packages, and npm(upgrade to latest version) and .NET Core to run it.

References: