NetCoreHeroes: Angular 2 with .Net Core in Visual Studio 2015, Part I

Georg Dangl by Georg Dangl in Web Development Friday, June 24, 2016

Friday, June 24, 2016

Posted in DotNet Angular2

The source code for this project is available at GitHub and here's a live example of the website.

Here's my attempt at creating the Angular 2 tutorial with a .Net Core application, fully developed in Visual Studio 2015 2017 and automatically tested and deployed via Jenkins. I'll not aim to rephrase what is already being covered by the Angular 2 tutorial itself but will focus on building the app with Visual Studio and try to highlight where something must be done differently. Right now, it's on .Net Core 1.0.0 1.0.1 (You should upgrade!) 1.1.0 1.1.2 2.0 and Angular 2 RC3 RC4 RC5 RC6 Final 4.1.3 5.2.3, but I will write about migrations when there are updates. You can read about what I had to change for each migration in the commits on GitHub.

To start, I've decided to go with a new Asp.Net Core (.Net Core) project and chose the Web Api template. Initially, you'll have to add a new controller which will act as your entry point.

This first part will cover how to set up Visual Studio 2015 2017 for Angular 2 TypeScript development.

Set Up a New .Net Core Web Application

using Microsoft.AspNetCore.Mvc;

namespace NetCoreHeroes.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index(string angularUrl)
        {
            ViewData["AngularBase"] = Url.Action(nameof(Index), new { angularUrl = string.Empty });
            return View();
        }
    }
}

The Index action has a catch-all parameter of type string so it matches angulars routed urls. The base path of the application is put into the ViewData["AngularBase"] bag, so that we can set the <base /> Html tag for Angular 2 to make use of its built in routing.

To match this action to your websites root directory, use the following route configuration when adding the UseMvcMiddleware in your Startup class:

app.UseMvc(routes =>
{
    routes.MapRoute("angular2SPA", "{angularUrl?}", new { controller = "Home", action = "Index" });
});

Then, add a View to Views/Home/Index.cshtml with the following content:

<!DOCTYPE html>
<html>
<head>
    <base href="@ViewData["AngularBase"]" />
</head>
<body>

</body>
</html>

 

Configure the Project for Angular 2

Developing with TypeScript in Visual Studio

Since I'm going to develop with TypeScript, I'll need to have the Microsoft TypeScript for Visual Studio extension. There's been a bug in the extensions that prohibits the discovery of a few dependencies, but luckily there's a workaround available for that. The next release after 1.8.34.0 of the TypeScript plugin should resolve that issue. Please note that updating to Visual Studio 2015 Update 3 will make it necessary to apply the workaround again.

Update 27.07.2016: This bug is fixed since TypeScript 2.0 beta, but beware if you have already applied this fix, the update for TypeScript will not update the typescriptServices.js file, see here for more details.

Angular 2 npm Packages

There's going to be a few changes in the package.json file for the node modules in contrast to the original Angular 2 tutorial. First, lite-server and concurrently can be omitted from the devDependencies since we're using .Net Core as server. Second, angular2-in-memory-web-api is no longer required as dependency since there'll be a small controller on the server to handle the heroes, thus we don't need to mock http services. Finally, the start and lite scripts can be removed since we're not going to need a local server when we've got .Net Core for that. TypeScript compilation is also handled by Visual Studio with live updates when changes are saved. Additionally add gulp and merge-stream to the devDependencies (as long as you still can 😉).

Note that we'll need the es6-shim package so that when we run headless Angular 2 unit tests in PhantomJS, we're compatible with ECMAscript 6 annotations. This is necessery for Angular 2 internals to work correctly.

Also, keep zone.js fixed on version 0.6.13 to avoid a bug in later versions that's not yet fixed. That's fixed now

The final packages.json should look like this:

{
  "name": "net-core-heroes",
  "version": "1.0.0",
  "scripts": {
    "postinstall": "typings install && gulp copyClientDeps",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "typings": "typings"
  },
  "license": "ISC",
  "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",
    "systemjs": "0.19.27",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "^0.6.23",
    "es6-shim": "0.35.1" 
  },
  "devDependencies": {
    "typescript": "^1.8.10",
    "typings": "^1.0.4",
    "gulp": "3.9.1",
    "merge-stream": "1.0.0"
  }
}

TypeScript Configuration File

In the tsconfig.json file, we'll have to add the option to compile on file save as well as exclude some folders that we do want the compiler to ignore:

  • node_modules is obvious, but Visual Studio might try stupid things otherwise...
  • wwwroot/lib is where we'll copy the dependencies to, so it should be ignored by the TypeScript compiler
  • bin and obj are directories generated during the build or publish process and should be ignored

The final file:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "compileOnSave": true,
  "exclude": [
    "node_modules",
    "wwwroot/lib",
    "bin",
    "obj"
  ]
}

typings.json

Just go with the one provided in the Angular 2 tutorial, but make sure to run npm run typings install after you've added the file, since Visual Studio will have automatically restored the npm packages already and there hasn't been a typings.json file for the typings installer.

systemjs.config.js

You can remove the entry for angular2-in-memory-web-api, since we'll not be using that. Further, replace the node_modules entry in paths with lib, since that's where we'll store our client side dependencies. Be sure to create the systemjs.config.js below the wwwroot folder, it must be accessible as static file on runtime. Finally, add the paths to the following testing bundles that we'll need later: @angular/core/testing@angular/compiler/testing@angular/platform-browser/testing, @angular/platform-browser-dynamic/testing and @angular/http/testing.

/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'lib:': 'lib/'
        },
        defaultJSExtensions: true,
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',
            // angular bundles
            '@angular/core': 'lib:@angular/core/bundles/core.umd.js',
            '@angular/core/testing': 'lib:@angular/core/bundles/core-testing.umd.js',
            '@angular/common': 'lib:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'lib:@angular/compiler/bundles/compiler.umd.js',
            '@angular/compiler/testing': 'lib:@angular/compiler/bundles/compiler-testing.umd.js',
            '@angular/platform-browser': 'lib:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser/testing': 'lib:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
            '@angular/platform-browser-dynamic': 'lib:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/platform-browser-dynamic/testing': 'lib:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
            '@angular/http': 'lib:@angular/http/bundles/http.umd.js',
            '@angular/http/testing': 'lib:@angular/http/bundles/http-testing.umd.js',
            '@angular/router': 'lib:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'lib:@angular/forms/bundles/forms.umd.js',
            // other libraries
            'rxjs': 'lib:rxjs'
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './main.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        }
    });
})(this);

gulp to Manage Client Dependencies

Since, in contrast to what the Angular 2 tutorial assumes, we'll be inside the wwwroot folder and therefore have node_modules one level up, we should use a taskrunner like gulp to copy the client dependencies inside our wwwroot folder so it's being published as static files to the website. Add this gulpfile.js to the project root:

var gulp = require('gulp');
var merge = require('merge-stream');

var paths = {
    nodeModules: "./node_modules/",
    clientDeps: "./wwwroot/lib/"
};

var clientLibraries = [
    "core-js",
    "zone.js",
    "reflect-metadata",
    "systemjs",
    "@angular",
    "rxjs",
    "es6-shim"
];

gulp.task("copyClientDeps",
    function () {
        var mergeStream = merge();
        for (var i = 0; i < clientLibraries.length; i++) {
            mergeStream.add(gulp.src([paths.nodeModules + clientLibraries[i] + "/**/*.js"])
                .pipe(gulp.dest(paths.clientDeps + clientLibraries[i])));
        }
        return mergeStream;
    });
Tip: If you're using ReSharper 2016.2, pay attention that I'm only copying *.js files via the glob pattern to the wwwroot/lib folder to minimize the amount of project files. Even if you're using your .csproj to exclode the wwwroot/lib folder and tell ReSharper to ignore the folder, it's still being accessed while analyzing your whole solution and caused for me (on three different machines) crashes in Visual Studio.

This task will copy the libraries that we need automatically from node_modules to the wwwroot/lib folder. To have it run automatically after node packages are installed, change the npm postinstall script to include the gulp task like this: "postinstall": "typings install && gulp copyClientDeps". The task will skip all tsconfig.json files from dependencies on copying, since that causes havoc with the Visual Studio TypeScript compiler.

Congratulations, the project should now basically be set up to allow developing Angular 2 apps in Visual Studio!

Update 27.07.2016: Updated Angular 2 to RC4 and changed the routing in Asp.Net Core to have the Angular 2 app available at the root directory. Also added note about TypeScript 2.0 upgrade

Update 30.08.2016: Updated Angular 2 to RC5

Update 04.09.2016: Updated Angular 2 to RC6

Update 22.09.2016: Updated Angular 2 to 2.0.0 Final and Asp.Net Core to 1.0.1

Update 23.11.2016: Updated Asp.Net Core to 1.1.0

Update 21.02.2017: The demo project is now using webpack as module loader, see here (and on GitHub) how to upgrade

Update 28.05.2017: Updated Angular to 4.1.3 and Asp.Net Core to 1.1.2. Switched to Visual Studio 2017 csproj format

Update 03.02.2018: Updated Angular to 5.2.3 and Asp.Net Core to 2.0


Share this post


comments powered by Disqus

About me

Hi, my name's George! I love coding and blogging about it. I focus on all things around .Net, Web Development and DevOps.

DanglIT

Need a partner for DevOps, Web Services or Software Development?

Contact me at [email protected], +49 (173) 56 45 689 or visit my professional page!

Dangl.Blog();
// Just 💗 Coding

Social Links