The Practical Client
Set Up ASP.NET MVC with AngularJS in Visual Studio
Here’s how to add AngularJS to an ASP.NET MVC application in Visual Studio 2015.
In previous columns I’ve looked at using TypeScript with popular JavaScript frameworks like Knockout and Backbone. It makes sense, therefore, to look at how to use TypeScript with one of the most popular JavaScript frameworks: AngularJS 2 (Angular 2).
Using Angular 2 in an ASP.NET MVC can involve configuring your computer, Visual Studio, your ASP.NET MVC project, Angular itself and TypeScript. Not surprisingly, doing that and creating a simple "Hello World" application is going to take all of this column (in later columns, I look at using TypeScript to actually create Angular applications).
I used Visual Studio 2015 Community Edition and the current versions of ASP.NET MVC, Angular 2 and TypeScript for this project.
Configuring Your Computer and Visual Studio
Angular 2 works best when it retrieves its components using the Node.js package manager (npm). Your first step, therefore, is making sure that you have Node.js version 4.6.x, or greater, installed on your computer (Node.js is installed with some versions of Visual Studio, but it can be a very old version).
To verify that you’re running Node.js version 4.6.x, open a command window and type:
node -v
If you get a "not recognized" message then you’ll need to install Node.js. Even if you have an acceptable version of Node.js, you must also have npm version 3.x.x or greater. To find that out, type this into your command window:
npm -v
If you have an earlier version of either Node.js or npm then you should upgrade to the latest versions with this command in your command window:
npm install npm@latest -g
Finally, you should clear out the npm cache (I found that if I skipped this step I got errors when unpacking Node.js packages):
npm cache clean
Configuring Visual Studio and TypeScript
You’ll also need to get the latest version of TypeScript. You can get it here or, because you have a command window open, you can use npm and type this:
npm install -g typescript
Even if you have Visual Studio 2015 Community Edition, you’ll need to have Update 3 installed. To check that, open Visual Studio and select Help | About Microsoft Visual Studio from the menu to determine if your version matches mine. If you don’t have Update 3, use Tools | Extensions from the menu, go to Updates and apply Update 3.
You also need to configure the Visual Studio project options to ensure that Visual Studio will use the versions of Node.js and npm that you’ve installed. To do that, select Tools | Options from the menu and, in the tree on the left, select Projects and Solutions | External Web Tools. In the panel on the right, move the $(PATH) entry so that it’s above the $(DevEnvDir) entries. Close the dialog when you’re done and restart Visual Studio.
Configuring Your Project
Now, you’re ready to add your ASP.NET MVC project. I used ASP.NET MVC 5.2.3. To make sure that you’re using that version of ASP.NET MVC (or later) use NuGet Manager to install ASP.NET MVC into your project.
To create my ASP.NET MVC project, I selected File | New | Project to open the New Projects dialog. Under Visual C# | Web, I selected ASP.NET Web Application (.NET Framework), gave my project a name, and clicked the OK button. In the resulting New ASP.NET Web Application, I selected MVC and checked off the MVC option. At this point, if you’re following along, you have a vanilla ASP.NET MVC 5.2.3 project.
To add my first controller, I used Add | New item and selected MVC 5 Controller – Empty. I called my controller HomeController.cs and, from its Index method, added a View called Index.cshtml. I added my View without a layout page to simplify coding later in the project.
Your next step is to let Node.js populate a node_modules folder in your project directory with the JavaScript files you need (this folder doesn’t need to be added to your project). This process is driven by the entries in a package.json file that must be part of your project. To add that file, right-click on your project and select Add | New Item to display the Add New Item dialog. In the dialog, select an npm Configuration File and click the Add button. Once the file is added, paste the text in Listing 1 into the file.
Listing 1: Package.json File Required for Angular 2 in ASP.NET MVC
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"scripts": {
"build": "tsc -p src/",
"build:watch": "tsc -p src/ -w",
"build:e2e": "tsc -p e2e/",
"serve": "lite-server -c=bs-config.json",
"serve:e2e": "lite-server -c=bs-config.e2e.json",
"prestart": "npm run build",
"start": "concurrently \"npm run build:watch\" \"npm run serve\"",
"pree2e": "npm run build:e2e",
"e2e": "concurrently \"npm run serve:e2e\" \"npm run protractor\" --kill-others
--success first",
"preprotractor": "webdriver-manager update",
"protractor": "protractor protractor.config.js",
"pretest": "npm run build",
"test": "concurrently \"npm run build:watch\" \"karma start karma.conf.js\"",
"pretest:once": "npm run build",
"test:once": "karma start karma.conf.js --single-run",
"lint": "tslint ./src/**/*.ts -t verbose"
},
"dependencies": {
"@angular/common": "~2.4.0",
"@angular/compiler": "~2.4.0",
"@angular/core": "~2.4.0",
"@angular/forms": "~2.4.0",
"@angular/http": "~2.4.0",
"@angular/platform-browser": "~2.4.0",
"@angular/platform-browser-dynamic": "~2.4.0",
"@angular/router": "~3.4.0",
"angular-in-memory-web-api": "~0.2.4",
"systemjs": "0.19.40",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.10",
"rxjs": "5.0.1",
"zone.js": "^0.7.4"
},
"devDependencies": {
"concurrently": "^3.2.0",
"lite-server": "^2.2.2",
"typescript": "~2.0.10",
"canonical-path": "0.0.2",
"tslint": "^3.15.1",
"lodash": "^4.16.4",
"jasmine-core": "~2.4.1",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~4.0.14",
"rimraf": "^2.5.4",
"@types/node": "^6.0.46",
"@types/jasmine": "2.5.36"
}
}
I’m not suggesting that I know that you need all the packages in Listing 1. I’m saying that my project worked with these packages.
To start the process of creating the node_modules folder and downloading all your packages, all you should have to do is save the package.json file. You can check out your progress in the View | Output window (provided you change the dropdown list at the top of the window to Bower/npm).
Be patient: It can take some time for the process to start and even longer for it to complete. You can check if the process is complete by looking for a node_modules folder in your project directory (because you haven’t added the folder to your project, it won’t appear in Solution Explorer). Once you’ve ensured that the folder isn’t empty, Node.js is done.
If simply saving the package.json file doesn’t work for you, right-click on package.json in Solution explorer and select Restore Packages. And, if that doesn’t work, open the CMD window, surf to your project folder and use this command:
npm install
From the Command Window, npm will automatically look for your package.json file in the current folder and use its contents to populate the node_modules folder.
Adding the JavaScript Application
Your first step in setting up the client-side part of your application is to add a JavaScript file called systemjs.config.js to your Scripts folder. Listing 2 shows what you need in that file to configure your Angular 2 application (again, I’m not saying everything in this file is necessary).
Listing 2: JavaScript Configuration File
function (global) {
System.config({
paths: {
// Paths serve as alias
'npm:': 'node_modules/'
},
// 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',
// Other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api':
'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
},
// Packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
}
}
});
})(this);
I added a new folder called "app" to my project’s Scripts folder to hold my application. You don’t have to call the folder "app," but it’s an Angular 2 convention to do so and that’s the folder that’s referenced in my sample systemjs.config.js code.
In your application folder, you should add three TypeScript files. Those files will hold:
- The definition of your application’s modules, which are the highest level of an Angular 2 application (conventionally called app.module.ts)
- The definition of your module’s components (conventionally called app.component.ts)
- The bootstrap code that Angular 2 will use to load your modules (conventionally called main.ts)
You can add these files in any order, but you might as well start from the top with your module file. To do that, add a TypeScript file named app.module.ts to your Scripts folder. Here’s a basic module definition for this file:
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 first two lines import Angular 2 modules that you need as part of defining your module. The third line imports your component file. The @NgModule block specifies the components in the module. In this case, there are three components: The Angular 2 BrowserModule and a component that’s used both to provide data declarations and to be the start point of the application (called AppComponent in my example). The last line exposes this definition to other TypeScript modules.
Again, you don’t have to call the component AppComponent, though this is a TypeScript convention. You’ll get some errors at this point. Don’t panic! The errors related to app.component and AppComponent will be fixed when you add your component file to the project; the message about "Experimental support for decorators…" associated with the last line will go away after you’ve run your application once.
Your next step is to define the components in your application with a TypeScript file called app.component.ts to your Scripts folder (the file’s name is another Angular convention). Typical code for this file looks like this:
import { Component } from '@angular/core';
@Component({
selector: 'HelloWorld',
template: '<h1>Page Says: {{text}}</h1>'
})
export class AppComponent { text = 'Hello, World'; }
The first line imports an Angular module that you need in this file. The next lines define the parts of this component. In this example, I define a:
- selector: A custom HTML tag I can use to invoke Angular 2 functionality (called HelloWorld, in this case)
- template: Some HTML to replace my custom tag whenever it’s found in a page
One thing to be careful of: The delimiters used in the template property in the previous code are not quotes. They’re backticks. You can type them in using the key to the left of the digit 1 at the top of your keyboard (at least, that’s where they are on my keyboard).
Inside a template, you use handlebars ( {{ }} ) to define an Angular 2 placeholder. At runtime, in this example {{text}} will be replaced by data associated with the text property in the component that uses this template. In the last line of the component file, I define my component with a property called text and set the text property to "Hello, World."
Finally, you can create the TypeScript code that will load your application. Conventionally, that file is called main.ts. A minimal main.ts looks like this:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
The first line in this file imports an Angular 2 component that you need in this file. The second line imports your module from your module file (that is, it must match the name in the export class line of your module file). The last line starts your application and uses the name from the previous import statement.
Configuring TypeScript
Now that you have your Angular 2 application set up, you need to configure TypeScript. To do that, add a tsconfig.json file to your project and put the contents of Listing 3 in it (in the Add New Item dialog, this file is listed as a "TypeScript JSON Configuration File").
Listing 3: Angular-Compatible TypeScript Configuration
{
"compilerOptions": {
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules",
"wwwroot"
]
}
Now would also be a good time to go to NuGet Manager and upgrade all the NuGet packages in your project.
Creating Your View
Finally, you’re ready to create a View that will work with Angular 2. Begin by adding these script elements to your View’s header element (if your View has a layout page, you should add these tags to the head element of your layout page):
<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="~/Scripts/systemjs.config.js"></script>
Also in head element, you should add the JavaScript code that, first, tells Angular 2 to look for the JavaScript version of your TypeScript files and, second, runs the code in your bootstrap file. That code looks like this:
<script>
System.config({
defaultJSExtensions: true
});
System.import('./scripts/app/main').catch(function (err) { console.error(err); });
</script>
The last step is to use the custom tag you set up in your component file to invoke Angular 2 processing (in my case, that’s a tag called HelloWorld). At run time, Angular 2 will replace the tag with the content of your template and then replace the data associated with the text property. My tag is this simple:
<HelloWorld/>
After doing all that, if you press F5 you should (after a while) see your page display with your content.
For now, that's a great start. Next month, I’ll start building out something more useful.