Using Angular 2.0 in Microsoft Visual Studio 2015

Published Mar 20, 2017
Using Angular 2.0 in Microsoft Visual Studio 2015

Purpose

This tutorial aims to help you get started with Angular 2 in ASP.NET Core using Visual Studio 2015. The release of Angular 2 and ASP.NET Core RC has made building an SPA interesting.

I have compiled the following steps to help you learn Angular 2. Hopefully you're feel much more relaxed at the end of this detailed post!

Step 1: Creating an empty ASP.NET Core project

Open Visual Studio 2015 Community Edition Update 3. Select "New Web Project" and name it ngCoreContacts. Then, select an “Empty” project template. Don’t forget to install the new web tools for ASP.NET Core 1.0
Creating Empty ASP.NET Core Application

I used Visual Studio 2015 Community Edition Update 3 (must), TypeScript 2.0 (must), the latest NPM, and Gulp.

Step 2: Configure ASP.NET Core to serve Static Files

ASP.NET Core is designed as a pluggable framework — it only includes necessary packages.

Lets create an HTML file named index.html under the wwwroot folder.

Right click on the wwwroot folder, add a "New Item," and create an index.html file. This HTML page will act as our default page.

<html>
<head>
    <meta charset="utf-8" />
    <title>Angular 2 with ASP.NET Core</title>
</head>
<body>
    <h1>Demo of Angular 2 using ASP.NET Core with Visual Studio 2015</h1>
</body>
</html>

For ASP.NET Core to serve static files, we need to add Static Files middleware in the Configure method of Startup.cs page. Ensure that packages are restored properly.

project.json is redesigned to make it better. We have Static Files middleware to serve static assets like HTML, JS files etc.

public void Configure(IApplicationBuilder app)
        {
            app.UseDefaultFiles();
            app.UseStaticFiles();
        }


{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.1",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0"
  },
 
  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  },
 
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },
 
  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true,
    "compile": {
      "exclude": [ "node_modules" ]
    }
  },
 
  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },
 
  "publishOptions": {
    "include": [
      "wwwroot",
      "web.config"
    ]
  },
 
  "scripts": {    
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

Interestingly, Program.cs is the entry point of application execution, just like void main().

Run the application now and ASP.NET Core will render the static HTML page.

Delete the index.html page — we will be injecting this dynamically later.

What I've just demonstrated is the “wwwroot“ as root folder for ASP.NET Core web apps.

Step 3: Angular 2 in ASP.NET Core

Angular 2 has taken pride in being ONE Framework for both MOBILE and DESKTOP apps. There won’t be any groundbreaking changes after its final release.

This tutorial was inspired by the 5 MIN QUICK START, which focused more on other light weight code editors. Here, we want to use Visual Studio 2015 Community Edition Update 3 for its built in TypeScript tooling and other features.

We will be using Webpack for module bundler — it’s an excellent alternative to the SystemJS approach. Read webpack and Angular 2 from more details.

The majority of webpack scripting is based on AngularClass’s angular2-webpack-starter. I have modified it here according to ASP.NET Core web apps.

The ASP.NET Core used here is SPA type of website, not MVC.

Why did I choose Webpack for Angular 2 in ASP.NET Core?

  • Webpack is much simpler to code and its plugins work with static files.
  • It's easy to run applcations in memory, live reloading, and compilation using Webpack 2, with dev-server.
  • It provides tree shaking to eliminate unused code.
  • Integrating with 3rd party packages like Angular Material 2, AngularFire, and bootstrap are just one line inclusion
  • AngularClass webpack starter kit provides HMR (Hot Module Replacement) – maintain execution state while code gets modified.
  • Webpack is adopted by Angular CLI, which is the AngularService by Microsoft that makes bundles smaller.

Step 4: Adding NPM Configuration file for Angular 2 packages

The Angular 2 team is pushing the code changes using NPM rather than CDN or any other source. Because of this, we need to add NPM configuration file (package.json) to the ASP.NET Core application.

Right Click on “ngCoreContacts“, add new file “NPM Configuration File“ — by default, package.json is added to ASP.NET Core project. This is the Node Package Manager (NPM) file, a must for adding packages for Angular 2

From the Angular 2 Quick start provided above, we need to add dependencies that are required for Angular 2 in ASP.NET Core application. Copy and paste the following configuration in package.json file:

{
    "version": "1.0.0",
    "description": "ngcorecontacts",
    "main": "wwwroot/index.html",
  "scripts": {
    "build:dev": "webpack --config config/webpack.dev.js --progress --profile",    
    "build:prod": "webpack --config config/webpack.prod.js  --progress --profile --bail",
    "build": "npm run build:dev",    
    "server:dev:hmr": "npm run server:dev -- --inline --hot",
    "server:dev": "webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base clientsrc/",
    "server:prod": "http-server dist --cors",
    "server": "npm run server:dev",
    "start:hmr": "npm run server:dev:hmr",
    "start": "npm run server:dev",
    "version": "npm run build",
    "watch:dev:hmr": "npm run watch:dev -- --hot",
    "watch:dev": "npm run build:dev -- --watch",
    "watch:prod": "npm run build:prod -- --watch",
    "watch:test": "npm run test -- --auto-watch --no-single-run",
    "watch": "npm run watch:dev",    
    "webpack-dev-server": "webpack-dev-server",
    "webpack": "webpack"
  },
  "dependencies": {
    "@angular/common": "~2.0.1",
    "@angular/compiler": "~2.0.1",
    "@angular/core": "~2.0.1",
    "@angular/forms": "~2.0.1",
    "@angular/http": "~2.0.1",
    "@angular/platform-browser": "~2.0.1",
    "@angular/platform-browser-dynamic": "~2.0.1",
    "@angular/router": "~3.0.1",
    "@angular/upgrade": "~2.0.1",
    "angular-in-memory-web-api": "~0.1.1",
    "@angularclass/conventions-loader": "^1.0.2",
    "@angularclass/hmr": "~1.2.0",
    "@angularclass/hmr-loader": "~3.0.2",
    "@angularclass/request-idle-callback": "^1.0.7",
    "@angularclass/webpack-toolkit": "^1.3.3",
    "assets-webpack-plugin": "^3.4.0",
    "core-js": "^2.4.1",
    "http-server": "^0.9.0",
    "ie-shim": "^0.1.0",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "~0.6.17",
    "@angular/material": "^2.0.0-alpha.9",
    "hammerjs": "^2.0.8"
  },
  "devDependencies": {
    "@types/hammerjs": "^2.0.33",
    "@types/jasmine": "^2.2.34",
    "@types/node": "^6.0.38",
    "@types/source-map": "^0.1.27",
    "@types/uglify-js": "^2.0.27",
    "@types/webpack": "^1.12.34",
    "angular2-template-loader": "^0.5.0",
    "awesome-typescript-loader": "^2.2.1",
    "codelyzer": "~0.0.28",
    "copy-webpack-plugin": "^3.0.1",
    "clean-webpack-plugin": "^0.1.10",
    "css-loader": "^0.25.0",
    "exports-loader": "^0.6.3",
    "expose-loader": "^0.7.1",
    "file-loader": "^0.9.0",
    "gh-pages": "^0.11.0",
    "html-webpack-plugin": "^2.21.0",
    "imports-loader": "^0.6.5",
    "json-loader": "^0.5.4",
    "parse5": "^1.3.2",
    "phantomjs": "^2.1.7",
    "raw-loader": "0.5.1",
    "rimraf": "^2.5.2",
    "source-map-loader": "^0.1.5",
    "string-replace-loader": "1.0.5",
    "style-loader": "^0.13.1",
    "sass-loader": "^3.1.2",    
    "to-string-loader": "^1.1.4",
    "ts-helpers": "1.1.1",
    "ts-node": "^1.3.0",
    "tslint": "3.15.1",
    "tslint-loader": "^2.1.3",
    "typedoc": "^0.4.5",
    "typescript": "2.0.3",
    "url-loader": "^0.5.7",
    "webpack": "2.1.0-beta.22",
    "webpack-dev-middleware": "^1.6.1",
    "webpack-dev-server": "^2.1.0-beta.2",
    "webpack-md5-hash": "^0.0.5",
    "webpack-merge": "^0.14.1"
  }
}
{
    "version": "1.0.0",
    "description": "ngcorecontacts",
    "main": "wwwroot/index.html",
  "scripts": {
    "build:dev": "webpack --config config/webpack.dev.js --progress --profile",    
    "build:prod": "webpack --config config/webpack.prod.js  --progress --profile --bail",
    "build": "npm run build:dev",    
    "server:dev:hmr": "npm run server:dev -- --inline --hot",
    "server:dev": "webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base clientsrc/",
    "server:prod": "http-server dist --cors",
    "server": "npm run server:dev",
    "start:hmr": "npm run server:dev:hmr",
    "start": "npm run server:dev",
    "version": "npm run build",
    "watch:dev:hmr": "npm run watch:dev -- --hot",
    "watch:dev": "npm run build:dev -- --watch",
    "watch:prod": "npm run build:prod -- --watch",
    "watch:test": "npm run test -- --auto-watch --no-single-run",
    "watch": "npm run watch:dev",    
    "webpack-dev-server": "webpack-dev-server",
    "webpack": "webpack"
  },
  "dependencies": {
    "@angular/common": "~2.0.1",
    "@angular/compiler": "~2.0.1",
    "@angular/core": "~2.0.1",
    "@angular/forms": "~2.0.1",
    "@angular/http": "~2.0.1",
    "@angular/platform-browser": "~2.0.1",
    "@angular/platform-browser-dynamic": "~2.0.1",
    "@angular/router": "~3.0.1",
    "@angular/upgrade": "~2.0.1",
    "angular-in-memory-web-api": "~0.1.1",
    "@angularclass/conventions-loader": "^1.0.2",
    "@angularclass/hmr": "~1.2.0",
    "@angularclass/hmr-loader": "~3.0.2",
    "@angularclass/request-idle-callback": "^1.0.7",
    "@angularclass/webpack-toolkit": "^1.3.3",
    "assets-webpack-plugin": "^3.4.0",
    "core-js": "^2.4.1",
    "http-server": "^0.9.0",
    "ie-shim": "^0.1.0",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "~0.6.17",
    "@angular/material": "^2.0.0-alpha.9",
    "hammerjs": "^2.0.8"
  },
  "devDependencies": {
    "@types/hammerjs": "^2.0.33",
    "@types/jasmine": "^2.2.34",
    "@types/node": "^6.0.38",
    "@types/source-map": "^0.1.27",
    "@types/uglify-js": "^2.0.27",
    "@types/webpack": "^1.12.34",
    "angular2-template-loader": "^0.5.0",
    "awesome-typescript-loader": "^2.2.1",
    "codelyzer": "~0.0.28",
    "copy-webpack-plugin": "^3.0.1",
    "clean-webpack-plugin": "^0.1.10",
    "css-loader": "^0.25.0",
    "exports-loader": "^0.6.3",
    "expose-loader": "^0.7.1",
    "file-loader": "^0.9.0",
    "gh-pages": "^0.11.0",
    "html-webpack-plugin": "^2.21.0",
    "imports-loader": "^0.6.5",
    "json-loader": "^0.5.4",
    "parse5": "^1.3.2",
    "phantomjs": "^2.1.7",
    "raw-loader": "0.5.1",
    "rimraf": "^2.5.2",
    "source-map-loader": "^0.1.5",
    "string-replace-loader": "1.0.5",
    "style-loader": "^0.13.1",
    "sass-loader": "^3.1.2",    
    "to-string-loader": "^1.1.4",
    "ts-helpers": "1.1.1",
    "ts-node": "^1.3.0",
    "tslint": "3.15.1",
    "tslint-loader": "^2.1.3",
    "typedoc": "^0.4.5",
    "typescript": "2.0.3",
    "url-loader": "^0.5.7",
    "webpack": "2.1.0-beta.22",
    "webpack-dev-middleware": "^1.6.1",
    "webpack-dev-server": "^2.1.0-beta.2",
    "webpack-md5-hash": "^0.0.5",
    "webpack-merge": "^0.14.1"
  }
}

Right after you save this, the ASP.NET Core will start restoring the packages. It would download all packages mentioned in the dependencies section of the above package.json.

Sometimes, in solution explorer, you might see ‘Dependencies – not installed’, don’t worry too much about this bug in tooling. All the npm packages should have been installed.

Step 5: Add TypeScript configuration file – a must for Angular 2 in ASP.NET Core using TypeScript

We are creating Angular 2 in ASP.NET Core starting with TypeScript. We should include TypeScript Configuration because of the following reasons: it can transpile to JavaScript, module loading, and target ES5 standards.

Refer to Getting Started with TypeScript if you want a beginner's guide on it.

Add “tsconfig.json” to the project, and copy & paste the following configuration.

P.S. “baseUrl” ensures that TypeScript files are transpiled to JavaScript from the ‘./clientsrc‘. This folder is a virtual directory for TypeScript

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "noEmitHelpers": true,
    "strictNullChecks": false,
    "baseUrl": "./clientsrc",
    "paths": [],
    "lib": [
      "dom",
      "es6"
    ],
    "types": [
      "hammerjs",      
      "node",      
      "source-map",
      "uglify-js",
      "webpack"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "awesomeTypescriptLoaderOptions": {
    "forkChecker": true,
    "useWebpackText": true
  },
  "compileOnSave": false,
  "buildOnSave": false,
  "atom": { "rewriteTsconfig": false }


{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "noEmitHelpers": true,
    "strictNullChecks": false,
    "baseUrl": "./clientsrc",
    "paths": [],
    "lib": [
      "dom",
      "es6"
    ],
    "types": [
      "hammerjs",      
      "node",      
      "source-map",
      "uglify-js",
      "webpack"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "awesomeTypescriptLoaderOptions": {
    "forkChecker": true,
    "useWebpackText": true
  },
  "compileOnSave": false,
  "buildOnSave": false,
  "atom": { "rewriteTsconfig": false }
}

Keep in mind that it’s mandatory to install TypeScript 2.o in order to work with Angular 2.

As of now, typings.json is not required because we are using @types with TypeScript. However, if you're using any other packages, which might not have entries in @types, then typings.json has to be added.

Step 6: Usage of Webpack as module bundler

What is Webpack?

According to the official website,

Webpack is a powerful module bundler. A bundle is a JavaScript file that incorporates assets that belong together and should be served to the client in a response to a single file request. A bundle can include JavaScript, CSS styles, HTML, and almost any other kind of file.

Webpack roams over your application source code, looking for import statements, building a dependency graph, and emitting one (or more) bundles. With plugin “loaders” Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files.

In package.json, we have added “webpack“ packages as “devdependencies“. They will perform all bundling work.

What webpack does is that it writes in a JavaScript configuration file know as webpack.config.js. As always, the applications run in Development, Test and Production environment.

There are some common functionalities and some specific to environments. We will focus on development and production environment.

Development environment should have source maps for debugging TypeScript files, minifying bundles of JS, CSS etc. and other files that are not necessary.

Production environment should minify bundles to reduce loading time — keep in mind, you should not include source maps. Webpack 2 also does tree shaking, i.e. eliminate unused code to further reduce bundle sizes.

The entire source code is on my github repo — feel free to fork or clone to play with it.

Now let's look at webpack.config.js. It is based on the environment set process.env.NODE_ENV, and it runs either dev or prod configurations.

Webpack splits Angular 2 apps into 3 files:

  1. polyfills (to maintain backward compatibility with older browsers)
  2. vendors (all JS, CSS, HTML, SCSS, images, JSON etc into one file)
  3. boot (application specific files) resolve based on various file extensions

Webpack itself doesn’t know what to do with a non-JavaScript file. Therefore, we must teach it to process such files into JavaScript with loaders. For this, we have written loaders TS, HTML, JSON, Fonts, and images.

Any static assets placed in “clientsrc/assets” will be copied to assets folder using CopyWebpackPlugin.
CleanWebpackPlugin will clean the “wwwroot/dist” folder every time we run it, so that we get a fresh set of files.

Since we've deleted the index.html file already, the clientsrc/index.html will now be moved to wwwroot using HtmlWebpackPlugin. Additionally, Webpack will inject the bundle files (i.e. polyfills, vendor, boot JS files) and include them in HTML script reference.

Now, let’s look at webpack.dev.js for development purpose.

Run “webpack-dev-server”: this runs entire application in memory, and any changes to source file gets applied immediately.

It will load application in debug mode with source map. Everything runs in memory (i.e. HTML, js, static files) are loaded in memory.

Run the application on localhost 3000 port --> Port can be changed as your convenience -->
Now let’s see webpack.prod.js for production purpose --> Merges all the bundle files and copies to wwwroot -->
Minifies all files to load faster using UglifyJsPlugin plugin

Step 7: Writing Angular 2 application

We have created an ASP.NET Core app, added TSconfig file, and added the webpack configuration. Now, it’s time to write the Angular 2 application.

In the github repo, you can see the “clientsrc” folder. This contains the Angular 2 app, which gets bundled into using webpack configurations we wrote.

“Clientsrc“ folder has index.html, polyfills.browses.ts, vendor.browsers.ts, and mostly importantly boot.ts.

We have app folders that contain HTML, Angular 2 components, and root level module (app.module.ts), which gets loaded while bootstrapping application.

Some of the files might not be interesting now but we will focus them in other articles later.

Step 8: Running the application

Before you run the application, make sure you run the command “npm install”. This might not be neede, but it will ensure all packages are installed.

Now let’s run the application in development mode

From command line (directory should be same as package.json), type “npm start” & hit enter. It will start running the webpack-dev-server, which loads application and listens on localhost:3000.

On console, it will say “bundle is now VALID.” Go ahead and open a browser and navigate to http://localhost:3000 to see the application load.

Notice the wwwroot folder — we don’t see any files copied because everything is running in memory.

Now that application runs properly in your browser, let’s understand how Angular 2 app loads:

When the browser starts rendering the index.html page, it encounters <my-app>Loading…</my-app> tag.
Angular’s module platformBrowserDynamic will bootstrap clientsrc/app/AppModulethrough line platformBrowserDynamic().bootstrapModule(AppModule).
AppModule then loads the component app.component.ts, which is mentioned in @NgModule as bootstrap entry.
Clientsrc/src/Appcomponent then resolves the <my-app> tag as selector in it and renders UI with TypeScript code.

When we enter “npm start” in console to run the application, execution points script sections of package.json to below code:

webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base clientsrc/

This invokes webpack-dev-server, runs the development config, and watches for any changes in clientsrc folder. Any changes in this folder will reload application with changes.

Here, ASP.NET Core is just an HTML based web app, so you can run this app as npm start to use AngularClass features of reloading, webpack, and Hot module replacement feature.

Running the application in Production mode:

Assuming the application is now ready to be deployed, we need to have a PROD build. For this, run command

//builds app and copies in wwwroot
Npm run build:prod

Now, if you see wwwroot folder, we see the HTML, JS bundle files. This wwwroot folder can be deployed on any web server, like IIS or nginx.

You can either press F5 to run it from Visual Studio IDE or run command npm run server:prod.

Angular 2 in ASP.NET Core

Discover and read more posts from Syed Ikram Shah
get started