This tutorial is aimed at beginners starting Angular 2 in ASP.NET Core using Visual Studio 2015. I have compiled the steps involved in starting to learn Angular 2. This is largely a step-by-step explanation, and you will feel much more confident at the end of the article.
Open Visual Studio 2015 Community Edition Update 3, Select New Web Project naming it “ngCoreContacts” and select “Empty” project template. Don’t forget to install new web tools for ASP.NET Core 1.0
I used Visual Studio 2015 Community Edition Update 3, TypeScript 2.0, latest NPM, Gulp. For Visual Studio and TypeScript, I suggest that you use the same versions that I use in this guide.
ASP.NET Core is designed as pluggable framework to include and use only necessary packages, instead of including too many modules initially.
Let's create an HTML file named index.html
under wwwroot
folder.
Right click on wwwroot
folder, Add New Item
and create the index.html
file. This HTML page will act as default page.
1<html>
2<head>
3 <meta charset="utf-8" />
4 <title>Angular 2 with ASP.NET Core</title>
5</head>
6<body>
7 <h1>Demo of Angular 2 using ASP.NET Core with Visual Studio 2015</h1>
8</body>
9</html>
For ASP.NET Core to serve static files, we need to add StaticFiles middleware in the Configure method of the Startup.cs
page. Ensure that packages are restored properly.
project.json
is redesigned to make it better, we have StaticFiles middleware to serve static assets like HTML, JS files etc.
1public void Configure(IApplicationBuilder app)
2{
3 app.UseDefaultFiles();
4 app.UseStaticFiles();
5}
1{
2 "dependencies": {
3 "Microsoft.NETCore.App": {
4 "version": "1.0.1",
5 "type": "platform"
6 },
7 "Microsoft.AspNetCore.Diagnostics": "1.0.0",
8 "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
9 "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
10 "Microsoft.Extensions.Logging.Console": "1.0.0",
11 "Microsoft.AspNetCore.StaticFiles": "1.0.0"
12 },
13
14 "tools": {
15 "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
16 },
17
18 "frameworks": {
19 "netcoreapp1.0": {
20 "imports": ["dotnet5.6", "portable-net45+win8"]
21 }
22 },
23
24 "buildOptions": {
25 "emitEntryPoint": true,
26 "preserveCompilationContext": true,
27 "compile": {
28 "exclude": ["node_modules"]
29 }
30 },
31
32 "runtimeOptions": {
33 "configProperties": {
34 "System.GC.Server": true
35 }
36 },
37
38 "publishOptions": {
39 "include": ["wwwroot", "web.config"]
40 },
41
42 "scripts": {
43 "postpublish": [
44 "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
45 ]
46 }
47}
Interestingly Program.cs is an entry point of application execution just like main().
Run the application now. Notice that ASP.NET Core renders the static HTML page.
Delete this index.html
page, we will be injecting this dynamically later. Till now you saw demonstration of “wwwroot“ as root folder for ASP.NET Core web apps.
Angular 2 has famously claimed to be the ONE Framework for mobile and desktop apps.
This tutorial refers to 5 MIN QUICK START for getting started. The 5-minute guide is more focused on other light-weight code editors. However, we are using Visual Studio 2015 Community Edition Update 3 for its built-in TypeScript tooling and other features.
We will be using Webpack as our module bundler. It’s an excellent alternative to the systemJS approach. To learn more about Webpack, read Webpack and Angular 2
The majority of Webpack scripting is based on AngularClass’s angular2-webpack-starter. I have modified it according to ASP.NET Core web apps.
ASP.NET Core follows Single Page Application (SPA) architecture rather than a Model-View-Controller (MVC) framework.
Why Webpack for Angular 2 in ASP.NET Core? I could have probably used many other bundlers.
However, Webpack does have many perks:
The Angular 2 team is pushing the code changes using NPM rather than CDN or any other source. Due to this, we need to add an NPM configuration file (package.json
) to this 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 acts Node Package Manager (NPM) file, a must-have for adding packages when using Angular 2.
From the Angular 2 Quick Start provided above, we need to add required dependencies. Copy-paste the below configuration in your package.json
file
1{
2 "version": "1.0.0",
3 "description": "ngcorecontacts",
4 "main": "wwwroot/index.html",
5 "scripts": {
6 "build:dev": "webpack --config config/webpack.dev.js --progress --profile",
7 "build:prod": "webpack --config config/webpack.prod.js --progress --profile --bail",
8 "build": "npm run build:dev",
9 "server:dev:hmr": "npm run server:dev -- --inline --hot",
10 "server:dev": "webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base clientsrc/",
11 "server:prod": "http-server dist --cors",
12 "server": "npm run server:dev",
13 "start:hmr": "npm run server:dev:hmr",
14 "start": "npm run server:dev",
15 "version": "npm run build",
16 "watch:dev:hmr": "npm run watch:dev -- --hot",
17 "watch:dev": "npm run build:dev -- --watch",
18 "watch:prod": "npm run build:prod -- --watch",
19 "watch:test": "npm run test -- --auto-watch --no-single-run",
20 "watch": "npm run watch:dev",
21 "webpack-dev-server": "webpack-dev-server",
22 "webpack": "webpack"
23 },
24 "dependencies": {
25 "@angular/common": "~2.0.1",
26 "@angular/compiler": "~2.0.1",
27 "@angular/core": "~2.0.1",
28 "@angular/forms": "~2.0.1",
29 "@angular/http": "~2.0.1",
30 "@angular/platform-browser": "~2.0.1",
31 "@angular/platform-browser-dynamic": "~2.0.1",
32 "@angular/router": "~3.0.1",
33 "@angular/upgrade": "~2.0.1",
34 "angular-in-memory-web-api": "~0.1.1",
35 "@angularclass/conventions-loader": "^1.0.2",
36 "@angularclass/hmr": "~1.2.0",
37 "@angularclass/hmr-loader": "~3.0.2",
38 "@angularclass/request-idle-callback": "^1.0.7",
39 "@angularclass/webpack-toolkit": "^1.3.3",
40 "assets-webpack-plugin": "^3.4.0",
41 "core-js": "^2.4.1",
42 "http-server": "^0.9.0",
43 "ie-shim": "^0.1.0",
44 "rxjs": "5.0.0-beta.12",
45 "zone.js": "~0.6.17",
46 "@angular/material": "^2.0.0-alpha.9",
47 "hammerjs": "^2.0.8"
48 },
49 "devDependencies": {
50 "@types/hammerjs": "^2.0.33",
51 "@types/jasmine": "^2.2.34",
52 "@types/node": "^6.0.38",
53 "@types/source-map": "^0.1.27",
54 "@types/uglify-js": "^2.0.27",
55 "@types/webpack": "^1.12.34",
56 "angular2-template-loader": "^0.5.0",
57 "awesome-typescript-loader": "^2.2.1",
58 "codelyzer": "~0.0.28",
59 "copy-webpack-plugin": "^3.0.1",
60 "clean-webpack-plugin": "^0.1.10",
61 "css-loader": "^0.25.0",
62 "exports-loader": "^0.6.3",
63 "expose-loader": "^0.7.1",
64 "file-loader": "^0.9.0",
65 "gh-pages": "^0.11.0",
66 "html-webpack-plugin": "^2.21.0",
67 "imports-loader": "^0.6.5",
68 "json-loader": "^0.5.4",
69 "parse5": "^1.3.2",
70 "phantomjs": "^2.1.7",
71 "raw-loader": "0.5.1",
72 "rimraf": "^2.5.2",
73 "source-map-loader": "^0.1.5",
74 "string-replace-loader": "1.0.5",
75 "style-loader": "^0.13.1",
76 "sass-loader": "^3.1.2",
77 "to-string-loader": "^1.1.4",
78 "ts-helpers": "1.1.1",
79 "ts-node": "^1.3.0",
80 "tslint": "3.15.1",
81 "tslint-loader": "^2.1.3",
82 "typedoc": "^0.4.5",
83 "typescript": "2.0.3",
84 "url-loader": "^0.5.7",
85 "webpack": "2.1.0-beta.22",
86 "webpack-dev-middleware": "^1.6.1",
87 "webpack-dev-server": "^2.1.0-beta.2",
88 "webpack-md5-hash": "^0.0.5",
89 "webpack-merge": "^0.14.1"
90 }
91}
92{
93 "version": "1.0.0",
94 "description": "ngcorecontacts",
95 "main": "wwwroot/index.html",
96 "scripts": {
97 "build:dev": "webpack --config config/webpack.dev.js --progress --profile",
98 "build:prod": "webpack --config config/webpack.prod.js --progress --profile --bail",
99 "build": "npm run build:dev",
100 "server:dev:hmr": "npm run server:dev -- --inline --hot",
101 "server:dev": "webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base clientsrc/",
102 "server:prod": "http-server dist --cors",
103 "server": "npm run server:dev",
104 "start:hmr": "npm run server:dev:hmr",
105 "start": "npm run server:dev",
106 "version": "npm run build",
107 "watch:dev:hmr": "npm run watch:dev -- --hot",
108 "watch:dev": "npm run build:dev -- --watch",
109 "watch:prod": "npm run build:prod -- --watch",
110 "watch:test": "npm run test -- --auto-watch --no-single-run",
111 "watch": "npm run watch:dev",
112 "webpack-dev-server": "webpack-dev-server",
113 "webpack": "webpack"
114 },
115 "dependencies": {
116 "@angular/common": "~2.0.1",
117 "@angular/compiler": "~2.0.1",
118 "@angular/core": "~2.0.1",
119 "@angular/forms": "~2.0.1",
120 "@angular/http": "~2.0.1",
121 "@angular/platform-browser": "~2.0.1",
122 "@angular/platform-browser-dynamic": "~2.0.1",
123 "@angular/router": "~3.0.1",
124 "@angular/upgrade": "~2.0.1",
125 "angular-in-memory-web-api": "~0.1.1",
126 "@angularclass/conventions-loader": "^1.0.2",
127 "@angularclass/hmr": "~1.2.0",
128 "@angularclass/hmr-loader": "~3.0.2",
129 "@angularclass/request-idle-callback": "^1.0.7",
130 "@angularclass/webpack-toolkit": "^1.3.3",
131 "assets-webpack-plugin": "^3.4.0",
132 "core-js": "^2.4.1",
133 "http-server": "^0.9.0",
134 "ie-shim": "^0.1.0",
135 "rxjs": "5.0.0-beta.12",
136 "zone.js": "~0.6.17",
137 "@angular/material": "^2.0.0-alpha.9",
138 "hammerjs": "^2.0.8"
139 },
140 "devDependencies": {
141 "@types/hammerjs": "^2.0.33",
142 "@types/jasmine": "^2.2.34",
143 "@types/node": "^6.0.38",
144 "@types/source-map": "^0.1.27",
145 "@types/uglify-js": "^2.0.27",
146 "@types/webpack": "^1.12.34",
147 "angular2-template-loader": "^0.5.0",
148 "awesome-typescript-loader": "^2.2.1",
149 "codelyzer": "~0.0.28",
150 "copy-webpack-plugin": "^3.0.1",
151 "clean-webpack-plugin": "^0.1.10",
152 "css-loader": "^0.25.0",
153 "exports-loader": "^0.6.3",
154 "expose-loader": "^0.7.1",
155 "file-loader": "^0.9.0",
156 "gh-pages": "^0.11.0",
157 "html-webpack-plugin": "^2.21.0",
158 "imports-loader": "^0.6.5",
159 "json-loader": "^0.5.4",
160 "parse5": "^1.3.2",
161 "phantomjs": "^2.1.7",
162 "raw-loader": "0.5.1",
163 "rimraf": "^2.5.2",
164 "source-map-loader": "^0.1.5",
165 "string-replace-loader": "1.0.5",
166 "style-loader": "^0.13.1",
167 "sass-loader": "^3.1.2",
168 "to-string-loader": "^1.1.4",
169 "ts-helpers": "1.1.1",
170 "ts-node": "^1.3.0",
171 "tslint": "3.15.1",
172 "tslint-loader": "^2.1.3",
173 "typedoc": "^0.4.5",
174 "typescript": "2.0.3",
175 "url-loader": "^0.5.7",
176 "webpack": "2.1.0-beta.22",
177 "webpack-dev-middleware": "^1.6.1",
178 "webpack-dev-server": "^2.1.0-beta.2",
179 "webpack-md5-hash": "^0.0.5",
180 "webpack-merge": "^0.14.1"
181 }
182}
Right after saving this, ASP.NET Core starts restoring the packages. It will download all packages mentioned in the dependencies section of the package.json
file.
The Visual Studio Solution Explorer might read ‘Dependencies – not installed’. Don’t worry; all the npm packages have been installed. This is simply a bug in the tooling.
You must do this for Angular 2 in ASP.NET Core apps that use TypeScript. The TypeScript Configuration file handles transpiling our application to JavaScript, loads modules, and meets ES5 standards.
Refer to Getting Started with TypeScript if want a beginner intro on TypeScript configuration.
Add tsconfig.json
in the project, and add in the configuration below.
“baseUrl” ensures that TypeScript files are transpiled to JavaScript from the ‘./clientsrc‘. This folder is virtual directory for TypeScript
1{
2 "compilerOptions": {
3 "target": "es5",
4 "module": "commonjs",
5 "moduleResolution": "node",
6 "emitDecoratorMetadata": true,
7 "experimentalDecorators": true,
8 "allowSyntheticDefaultImports": true,
9 "sourceMap": true,
10 "noEmitHelpers": true,
11 "strictNullChecks": false,
12 "baseUrl": "./clientsrc",
13 "paths": [],
14 "lib": [
15 "dom",
16 "es6"
17 ],
18 "types": [
19 "hammerjs",
20 "node",
21 "source-map",
22 "uglify-js",
23 "webpack"
24 ]
25 },
26 "exclude": [
27 "node_modules",
28 "dist"
29 ],
30 "awesomeTypescriptLoaderOptions": {
31 "forkChecker": true,
32 "useWebpackText": true
33 },
34 "compileOnSave": false,
35 "buildOnSave": false,
36 "atom": { "rewriteTsconfig": false }
37
38
39{
40 "compilerOptions": {
41 "target": "es5",
42 "module": "commonjs",
43 "moduleResolution": "node",
44 "emitDecoratorMetadata": true,
45 "experimentalDecorators": true,
46 "allowSyntheticDefaultImports": true,
47 "sourceMap": true,
48 "noEmitHelpers": true,
49 "strictNullChecks": false,
50 "baseUrl": "./clientsrc",
51 "paths": [],
52 "lib": [
53 "dom",
54 "es6"
55 ],
56 "types": [
57 "hammerjs",
58 "node",
59 "source-map",
60 "uglify-js",
61 "webpack"
62 ]
63 },
64 "exclude": [
65 "node_modules",
66 "dist"
67 ],
68 "awesomeTypescriptLoaderOptions": {
69 "forkChecker": true,
70 "useWebpackText": true
71 },
72 "compileOnSave": false,
73 "buildOnSave": false,
74 "atom": { "rewriteTsconfig": false }
75}
At present typings.json is not required because we are using @types with TypeScript. However if your using any other packages which don’t have entries in @types then typings.json has to be added.
Webpack is a powerful module bundler. A bundle is a JavaScript file that incorporate 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, HTML, and almost any other language.
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.
webpack.config.js
defines what Webpack does. As always, the applications are run in Development, Test and Production environment.
There are some common functionalities and some specific to environments. We will focus on the development and production environment.
The development environment should have source maps for debugging TypeScript files. However, minifying bundles of JS, CSS, etc. is not required. The production environment should minify bundles to reduce loading time. Webpack 2 also does tree-shaking, a method for eliminating unused code to shrink bundle sizes.
The entire source code is on my github repo, fork or clone to play with it.
webpack.config.js
– Based on environment set process.env.NODE_ENV, it runs either dev or prod configurations.
Webpack.common.js
before bundling environment specific files, it performs tasks meant to be used for both environment.
Webpack splits Angular 2 apps into 3 files polyfills
(to maintain backward compatibility with older browsers), vendors
(all JS, CSS, HTML, SCSS, images, JSON etc into one file) and boot
(application specific files).
This split is resolved based on various file extensions when Webpack itself doesn’t know what to do with a non-JavaScript file.
We teach it to process such files into JavaScript using loaders. For this, we have written loaders for TS, HTML, JSON, fonts, images, and more. Any static assets placed in clientsrc/assets
will be copied to assets folder using CopyWebpackPlugin. CleanWebpackPlugin cleans wwwroot/dist
folder every time we run it, so that we get fresh set of files.
I told you above to delete the index.html
file, now the clientsrc/index.html will be moved to wwwroot
using HtmlWebpackPlugin. Plus Webpack injects the bundle files i.e. polyfills, vendor, boot JS files and includes them in HTML script reference.
Now let’s see webpack.dev.js
for development purpose
Running webpack-dev-server
– this runs the entire application in memory. Any changes to source file gets applied immediately. Loads application in debug mode with source map. Runs the application on localhost 3000 port. (The port can be changed as your convenience.)
Now let’s take a look at what webpack.prod.js
does:
wwwroot
.Hopefully this clarifies some of Webpack's functionalities.
Until now we created ASP.NET Core app, added TSconfig file, 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 folder containing HTML, Angular 2 components and root level module (app.module.ts
) which gets loaded while bootstrapping application.
Some of files might be not interesting now, will focus them in separate articles later.
Before running make sure you have run command npm install
. This might not be needed but still it will ensure all packages are installed.
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.
When the console reads bundle is now VALID
, open a browser and navigate to http://localhost:3000
to see the loaded application
Notice wwwroot
folder, we don’t see any files copied because everything is running in memory.
Now that the application runs properly on browser, let’s understand how Angular 2 app loads.
When the browser starts rendering the index.html
page, it encounters the platformBrowserDynamic
bootstraps clientsrc/app/AppModule
through 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 a selector in it and renders the UI with TypeScript code.
When we enter npm start
in console to run the application, execution points scripts section of package.json
to below code
1webpack-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 the application with changes.
Here ASP.NET Core feels just like an HTML-based web app, so run using npm start to use AngularClass features of reloading, using webpack, Hot module replacement feature.
Running the application in Production mode
Assuming the application is now ready to deployed, we need to have the PROD build. For this run command
1//builds app and copies in wwwroot
2npm 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 do F5 to run from Visual Studio IDE or run command npm run server:prod
Hopefully this tutorial helped you create your Angular 2 app using Visual Studio. Thank you for reading!