Introduction
This is a note on setting the environment for Angular 2 & Typescript in Visual Studio.
Background
Angular 2 was officially released on 9/15/2016 that is a big thing. Compared to other similar libraries, i.e., jQuery, React, and Angular 1, Angular 2 is big. For example, if you want to use jQuery, all you need to do is to add one link to the jQuery CDN in your HTML page.
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
If you choose not to use a CDN, the size of the "jquery-3.1.1.min.js" file is just modestly 34.3 KB. But Angular 2 is big and proud. It needs to download 42.8 MB (more than 1000 times bigger than jQuery) of dependencies to work. It is not only big, it is also "agile" that changes very fast. From the Angular official website, you can get a copy of the quick-start example in Node.
This note assumes that you want to use Typescript, and hopefully we can keep a record on setting the environment to develop Angular 2 applications in Typescript. The Visual Studio used in this example is Visual Studio Community 2015 Update 2, version 14.0.25425.01.
Install NPM & Download Dependencies
The NPM Hell
We need NPM to download the Angular 2 dependencies. In your computer, you may have multiple instances of NPM with or without your knowledge. When we have multiple versions of DLLs on a computer, we have the notorious DLL hell. When we have multiple instances of NPM on a computer, we have a NPM hell. In order to survive the hell, we need know which NPM is used. NPM comes with Node. We can download and install Node from the official website. After the installation, you can check the NPM path by the following command in Windows.
You can check the NPM version by the following command:
NPM can update itself, so you can change the version of the NPM by the following command:
If you want to update NPM to the latest available version, you can issue the following command:
In this note, I used the version "3.10.9". If you want to check all the available versions of NPM in the NPM repository, you can use the following command:
Visual Studio comes with its own copy of NPM. In order that it uses our NPM instance, we can go to "Tools" -> "Options..." and search for "External Web Tools".
Download the Dependencies
If we now right click the "package.json" file -> "Restore Packages", the Visual Studio will download all the dependencies with the NPM instance specified. The "package.json" file tells NPM what to download.
{
"name": "angular-2-environment",
"version": "1.0.0",
"private": true,
"scripts": {
"postinstall": "typings install",
"typings": "typings"
},
"license": "MIT",
"dependencies": {
"@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",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.12",
"systemjs": "0.19.27",
"zone.js": "^0.6.23",
"angular2-in-memory-web-api": "0.0.20"
},
"devDependencies": {
"typings": "^1.3.2"
}
}
You may have noticed the "postinstall
" in the "scripts
" section. It tells NPM to download "typings" files after the "npm install
" command succeeds using the "typings
" utility specified in the "devDependencies
". The "typings
" are needed by the Typescript compiler to compile our Typescript code. The "typings.json" files tells the "typings
" utility what files to download.
{
"globalDependencies": {
"core-js": "registry:dt/core-js#0.0.0+20160725163759",
"node": "registry:dt/node#6.0.0+20160909174046"
}
}
If the "Restore Packages" succeeds, you should see two folders, "node_modules" and "typings" added in your project folder.
Since the two folders are added by NPM, they are not under the control of your Visual Studio project. You need to click on the "Show All Files" icon to see them. If you now check the size of the "node_modules" folder, you can see that it is proudly 42.8M.
The Typescript Compiler
When we have multiple instances of NPM in the computer, we have a NPM hell. If we have multiple instances of TSC (the Typescript compiler) with or without our knowledge, we may have a TSC hell. The Typescript compiler typically should compile our code under two situations:
- When we build the project, the TSC should compile the Typescript code.
- It is also a convenience to compile the Typescript code when we save a Typescript file, so we do not need to always rebuild the project during the test and debug phase.
If you have played with Angular 2 tutorials before, you may have seen the following "package.json" file.
{
"name": "angular-2-environment",
"version": "1.0.0",
"private": true,
"scripts": {
"postinstall": "typings install",
"typings": "typings",
"tsc": "tsc",
"tsc:w": "tsc -w",
},
"license": "MIT",
"dependencies": {
},
"devDependencies": {
"typings": "^1.3.2",
"typescript": "^2.0.2"
}
}
With this "package.json" file, NPM will download an instance of Typescript compiler that is accessible in the ".bin" folder in the local "node_modules" folder. This is typically not a problem. But unfortunately, Visual Studio comes with its own Typescript compiler. With the two instances of TCS on the computer, we do not have the absolute certainty which TSC is used in all the circumstances. In the Visual Studio environment, I choose to use the TSC comes with the Visual Studio and make sure all the compilations to go through this compiler.
If you want to checkout the version of TSC that comes with Visual Studio, you can go to "Tools" -> "Extensions and Updates..." -> "Installed" and search for "typescript". If you want to change the TSC version in the Visual Studio, you can go to "Tools" -> "Extensions and Updates..." -> "Online" and search for "typescript".
You can then choose the TSC version that you like and install it to your Visual Studio. You may need to re-start your Visual Studio to complete this process. You may have noticed that we have a TSC hell in Visual Studio, because we have so many versions of "TypeScript v# for Visual Studio 2015". To further make sure we are using the desired version, we need to take a look at the ".csproj" file.
You can right click the project name in the Visual Studio to "unload" the project and open the ".csproj" file. You should be able to see the "TypeScriptToolsVersion
" that instructs the desired version of TSC to use.
{
"compileOnSave": true,
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"rootDir": "./ngApp"
},
"exclude": [ "node_modules", "scripts" ]
}
The Typescript compiler in Visual Studio honors the "tsconfig.json" file in the project. In this example, we give the TSC the following instructions.
- Exclude any Typescript files in the "node_modules" and "scripts" folders, because we want TSC to only compile our code but not the code downloaded by NPM.
- TSC should look for Typescript files in the "ngApp" folder to compile. The compiled JavaScript files will be in the same "ngApp" folder.
- The compiled JavaScript file should be in "ES5" compatibility level.
- Whenever we save a Typescript file, TSC should be invoked to compile the changes in the file.
Although not explicitly specified, the TSC will compile the Typescript files when we build the project.
NPM Install & Typings Install
You should have noticed that we need to download files into both "node_modules" and "typings" folders. But you may not always succeed, particularly if your internet connection has a proxy server. In my case, I have no problem to run the "npm install
" for the same "package.json" in my home, but I failed to do the same in a corporate network. The reason is that many companies apply a proxy to the internet connections. This is another hell that vary case by case, because how the proxy server is setup is largely case by case. We may have two ways to solve this problem, but neither is perfect if you are lucky enough to get into this hell.
- If you are lucky enough to be able to download these two folders at least once, keep them locally so you do not need to download them again.
- The NPM & Typings repository should provide us a web page that we may be able to download the files using a web browser. It is like that we have elevators in a tall building, we should always have stairs in the same building in case of emergencies.
At the end, I wish you the best of the luck that you are not trapped in this hell.
Cache Control on the JS Files and HTML Templates
You should have noticed that all the compiled JavaScript files and the HTML templates will download to the browser to display the web pages. In some cases, caching can be a problem.
- During the development, if you make any changes to the components and HTML templates, you want the browser to download the new version, but not to use the cached copy.
- If you make a new installation, you want the browser to download the new version, but not to use the cached copy.
The ideal situation is that you can implement some cache control system that you can tell the browser to get the new copy when you know that it is changed and use the cached copy when you know that it is not changed. But this may be a tedious work. If you are willing to take the performance penalty, you can disable all the caching, so your web pages at least behave correctly. In ASP.NET applications, you can add the following function to the "Global.asax" file.
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
HttpContext.Current.Response.Cache.SetNoStore();
}
You need to also add the following to the "web.config" file to truly disable the cache, because Angular components and templates are static HTTP contents.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
A Simple Page with Angular 2 & Typescript
We have spent a lot of time to battle the NPM & TSC hells. You may get lucky if you choose not to worry about the hells. But if you do not want to get into problems at the time when you absolutely do not want problems, the battle is worth some effort. We are now ready to create a simple web page using Angular 2 and Typescript. In this simple web page, I will not go to the very details on Angular 2, because you can easily find many references on this subject.
A typical minimal Angular 2 application has three levels:
- One or more Angular components
- One or more Angular modules
- An application entry point. In this example, it is the "main.ts" file
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>Survive the HELLs!</h1>'
})
export class AppComponent {
constructor() {}
}
The "app.component.ts" implements an Angular component. All it does is to display some text on the web page.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
The "AppComponent
" is added to the "AppModule
" in the "app.module.ts" file. The "AppModule
" is then specified in the "main.ts" to start the Angular 2 application.
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
enableProdMode();
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
In order for the Angular 2 environment to know how to start the application, we need the "systemjs.config.js" file.
(function (global) {
System.config({
paths: {
'npm:': '/node_modules/'
},
map: {
app: '/ngApp/',
'@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',
'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
},
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
'angular2-in-memory-web-api': {
main: './index.js',
defaultExtension: 'js'
}
}
});
})(this);
- The "systemjs.config.js" file tells the Angular environment where to find the "
node_module
". It has the dependencies for an Angular application to run. - It also tells the Angular environment where to find our JavaScript files compiled from our Typescript files.
- At the end, it needs to tell the Angular environment which JavaScript file is the application start point. In this example, it is the "./main.js" file.
With all the effort, we can finally add the Angular component into our web page in the "Index.cshtml" file.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Angular 2 environment</title>
<!--
<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>
<!--
<script src="systemjs.config.js"></script>
<script>
System.import('app')
.catch(function(err){ console.error(err); });
</script>
</head>
<body>
<div><my-app></my-app></div>
</body>
</html>
Run the Application
If everything is well, you can build and run the example MVC application. When the page loads, you should see the Angular component is displayed in the web page.
If you open the development tool in the browser and take a look at the network traffic, you can see that to display the text "Survive the Hells
" on the web page, the browser actually made 31 proudly HTTP requests.
Deployment and Source Control
I do not want to spend a lot of time on the deployment and the source control issues here. How to address these problems can be case by case and largely personal preference. But with the big "node_modules" folder and the Typescript compiler, we have some problems to address.
Problems with the Source Control
- In a regular project, we normally do not check in the compiled DLL or EXE into the source control. With Typescript in play, we do not want to check in the compiled JavaScript files.
- But in a web application, we may have a lot of native JavaScript files, i.e, jQuery, Boostrap, and our own JavaScript files. We may need to check in these files so we do not lose them.
- We need to come up with a nice way to properly ignore the compiled JavaScript files. If you use GIT, you may need to figure our how to properly ".gitignore" these files. If you do not properly ignore these files, you will see a lot of merge issues with the source control system.
Problems with the Deployment
- Typically, it is not a big problem to deploy a web application to IIS. As a brutal force measure, you can simply copy all the files to IIS after a successful build. It should work if the IIS and web.config is configured properly.
- But the "node_modules" folder is so large, which is very likely a lot larger than all your own code combined. Do you need all of the files in the "node_modules" folder deployed to your web server? Are all the files in the "node_modules" folder needed at run-time?
- The web server needs the JavaScript files to run, but it does not need the Typescript files. Do you need to copy all the Typescript files to the web server?
- If you want to use "Wix" to create a installation package, the "node_modules" files may need to be added to the control of the ".csproj" file. Do you really want to add all the 42.8M files into your Visual Studio project?
The solution of these problems can be case by case. It is largely a personal preference. Even you do not find a very elegant solution, the brutal force solutions should always work.
Points of Interest
- This is a note on setting up the environment for Angular 2 & Typescript in Visual Studio.
- This note spent a lot of effort to wrestle the NPM hell and the Typescript hells. Being able to survive these hells is important. If you have multiple copy of NPM and TSC, it is important that you are certain which copy is used to do your work.
- Angular 2 is big. If you use the "package.json" file used by the official Angular 2 tutorial, you have a 42.8M "node_modules" folder to download.
- Angular 2 is big. For the text "
Survive the Hells
" to display on the web page in this note, it made 31 proudly HTTP requests. - Angular 2 is a good environment, the modular design can provide some big advantages to share the code and potentially benefit the unit tests.
- Angular 2 is both big and "Agile". But with enough effort, we can definitely "
Survive the Hells!
" and enjoy all the advantages that Angular 2 can possibly provide us. - I hope you like my postings and I hope this note can help you one way or the other.
History
- 12/11/2016: First revision