Your First Angular 2, ASP.NET Core Project in Visual Studio Code – Part 6

Angular2, ASP.NET Core, Visual Studio Code, Part 6

Derived from photo by Markus Spiske / raumrot.com, CC-BY

This is the last in a series of posts teaching you how to create your first Angular 2 application with ASP.NET Core in Visual Studio Code. Here is the list of posts:

This article discusses an important part of the integration between client and server – routing. Angular 2 has its own router and so does ASP.NET Core. In the following sections you learn to setup routing in Angular 2 and then how to support the Angular 2 routing with ASP.NET Core.

This post addresses the high-level concerns of integrating these two routing systems. For more information on the individual routing features for each framework, read about Angular 2 routing here and ASP.NET Core routing here.

.NET Core SDK Preview 3: This tutorial uses .NET Core SDK Preview 3 which is available on GitHub. This version includes 'alpha' support for MSBuild-based projects that use .csproj configuration instead of project.json configuration. Microsoft announced that MSBuild is the common project configuration for .NET Core based projects going forward. By using MSBuild, the goal is to better prepare you for future versions of the tooling.

If you would like to jump straight to the code, it's all on GitHub.

Refactor to Multiple Components

In order to implement routing for Angular 2, you need to have multiple components in the project.

Move the contents of AppComponent to a new component called PairingComponent which is responsible for pairing Sriracha with foods.

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-pairing',    
    template: `
        <h2>Pairing</h2>
        <input type="text" [value]="food" (input)="foodInput($event)"/>        
        <p>Sriracha sauce is great with {{ food }}</p>
    `,
})
export class PairingComponent { 
    food = 'kielbasa';

    foodInput(event: Event) {
        const target = event.target as HTMLInputElement;
        this.food = target.value;
    }
}

The AppComponent becomes the application shell including the navigation. For now, the PairingComponent is rendered directly in the AppComponent template.

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-app',    
    template: `
        <nav>
            <a href="/pairing">Pairing</a> | 
            <a href="/about">About</a>            
        </nav>
        <my-pairing></my-pairing>
    `,
})
export class AppComponent { }

Also, create a third component called AboutComponent which serves as a second view.

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-about',    
    template: `
        <h2>About Sriracha</h2>

        <p>This is what Wikipedia says about <a href="https://en.wikipedia.org/wiki/Sriracha_sauce_%28Huy_Fong_Foods%29">Sriracha sauce</a>:</p>

        <blockquote>It can be recognized by its bright red color and its packaging: a clear plastic bottle with a green cap, text in Vietnamese, English, Chinese, French, and Spanish, and the rooster logo. David Tran was born in 1945, the Year of the Rooster...<blockquote>
    `,
})
export class AboutComponent { }

Finally, you declare all of these components in the AppModule.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { PairingComponent } from './pairing.component';
import { AboutComponent } from './about.component';

@NgModule({
    bootstrap: [ AppComponent ],
    imports: [ 
        BrowserModule,
    ],
    declarations: [ 
        AppComponent, 
        PairingComponent, 
        AboutComponent, 
    ],
})
export class AppModule { }

The app directory now looks like this:

component folder structure

Run your application to see the new navigation.

Navigation Screenshot

Configure the Angular 2 Router

To configure the AppModule routes, import RouterModule and Routes from @angular/router. The Routes type is an array of Route objects used to define the routes.

When navigating to http://localhost:5000/pairing the application should show the PairingComponent. When navigation to http://localhost:5000/about the application should show the AboutComponent. There is also a third scenario. When navigating to the root of the application, http://localhost:5000/, the application should redirect to http://localhost:5000/pairing as the default.

This is what the Routes array looks like:

const routes: Routes = [
  { path: '', redirectTo: '/pairing', pathMatch: 'full' },
  { path: 'pairing',  component: PairingComponent },
  { path: 'about', component: AboutComponent },
];

The pathMatch property must be set for all redirects. In this case, the value 'full' denotes that the redirect should occur when the path is at the application root or is otherwise empty ('').

Next, register these routes with the RouterModule and register the RouterModule with the AppModule.

This is the final code for the AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { PairingComponent } from './pairing.component';
import { AboutComponent } from './about.component';

const routes: Routes = [
  { path: '', redirectTo: '/pairing', pathMatch: 'full' },
  { path: 'pairing',  component: PairingComponent },
  { path: 'about', component: AboutComponent },
];

@NgModule({
    bootstrap: [ AppComponent ],
    imports: [ 
        BrowserModule,
        RouterModule.forRoot(routes),
    ],
    declarations: [ 
        AppComponent, 
        PairingComponent, 
        AboutComponent, 
    ],
})
export class AppModule { }

Finally, the Angular router depends on a base element with href defined. You add this in the head of index.html so that Angular can update the browser's address bar correctly. This is the updated index.html code:

<!DOCTYPE html>
<html>

<head>
    <title>Angular 2, ASP.NET Core Starter</title>
    <base href="/">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Polyfill(s) for older browsers -->
    <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>
    <my-app>Loading...</my-app>
</body>

</html>

Configure the Navigation

The last step to configure the Angular routing is to configure the navigation to utilize the routes. To accomplish this, you modify the AppComponent template in two significant ways.

First, instead of including the my-pairing element in the AppComponent template, replace it with the router-outlet element. This is a placeholder where the router renders the component matching the current route.

Finally, you replace the href attribute with the routerLink attribute. This is a directive instructing Angular to register the click with the router instead of the default href behavior.

The final code for the AppComponent looks like this:

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-app',    
    template: `
        <nav>
            <a routerLink="/pairing">Pairing</a> | 
            <a routerLink="/about">About</a>            
        </nav>
        <router-outlet></router-outlet>
    `,
})
export class AppComponent { }

At this point, run the application using Ctrl+Shift+B and start the browser using npm run chrome which you configured in part 5. Notice how the application defaults to the /pairing URL. Clicking on the link loads the appropriate component for that specific URL.

Working Angular 2 Routes

There's still one problem. When you go directly to a route URL like http://localhost:5000/about from the browser's address bar, you see a 404 error. This is a result of the fact that even though Angular knows how to reconcile this URL, the server does not.

In the next section, you configure the ASP.NET MVC routes to return the Angular application when it doesn't recognize the route.

Add ASP.NET Routing

In order to configure ASP.NET routing, you must install the ASP.NET MVC package. Add this PackageReference to the .csproj file and then run dotnet restore from the terminal.

<PackageReference Include="Microsoft.AspNetCore.Mvc">
    <Version>1.1.0</Version>
</PackageReference>

Once the package is installed, create a directory at the root of your project called Controllers. Inside this directory, create a file called HomeController.cs. In this file, create a HomeController class which inherits Controller. This is the code.

using System.IO;
using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    public IActionResult Index()
    {
        var fileName = "index.html";
        var contentType = "text/html";

        string filePath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
        string fileContents = System.IO.File.ReadAllText(filePath);

        return Content(fileContents, contentType);
    }
}

In ASP.NET MVC, controllers are where a request is processed and a response is constructed. In this controller, the Index method reads the index.html file containing the Angular application and returns the file contents as a text/html response to the browser. This is essentially the same response that the browser receives when requesting index.html directly. Once the application is loaded into the browser, the Angular router takes over.

In order for the HomeController to do its thing, you must configure the ASP.NET MVC router to direct all of the incoming requests to the Index method.

In the Startup.cs file, you have to add a couple new configurations. First, ASP.NET MVC is configured as a service which requires the Startup class to implement a ConfigureServices method. Also, you configure the routes in the existing Configure method. The new Startup.cs code looks like this:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

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

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{*url}",
                defaults: new { controller = "Home", action = "Index" });
        });
    }
}

The MapRoute method takes a configuration object. The template field defines the URL pattern that matches the route. Because there is only one variable defined in the template and it uses the asterisk, this route essentially handles all requests that haven't already been handled by the UseDefaultFiles or UseStaticFiles middleware. Furthermore, the defaults field directs all of these requests to the HomeController and it's Index action method.

At this point, run your application. In the browser, go directly to http://localhost:5000/about and you will see that the application loads from the server. Once the Angular application loads, it applies the correct route on the client.

Routing Integration

The End

This brings the final part of this Angular 2, ASP.NET Core, and Visual Studio Code journey to a close. You have learned a lot about configuring these frameworks and tools to work together. You've learned your way around Visual Studio code. You configured an Angular 2 and ASP.NET Core application so that both frameworks play well together. You created a basic Angular 2 application and automated tasks with npm and Visual Studio Code.

But you have only scratched the surface. This is just the beginning. What was your favorite discovery? Which topics are you most interested in learning about more? Leave a comment below and have fun with your new tools!

24 thoughts on “Your First Angular 2, ASP.NET Core Project in Visual Studio Code – Part 6”

  1. Thanks for the great tutorial but
    please add a note that if
    is not added to the in the index.html page
    routing won’t work and will get this error when running chrome’s dev tools to debug:
    Unhandled Promise rejection: No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.
    Funny enough, when I checked your github version , it had it 😉

  2. Hi Aaron,

    Thanks for the great tutorial. One thing to note for ASP.NET routing, you can take advantage of the Microsoft.AspNet.SpaServices nuget library.

    This will allow you to use routes.MapSpaFallbackRoute method. This method will return a 404 if a static resource is missing. The code displayed above will always return index.html regardless.

  3. This is the best tutorial I have ever seen about Angular 2 and DotNet Core. I have not problem to run your code except changing the version number in the project file. I have one problem and one question.
    Problem: I am not able to make the break point work.
    Question: Why do the MVC routing is needed? I think dotnet just need to provide webApis.

    1. Thanks Victor! It looks like the debugging scenario used in conjunction with dotnet watch is not quite there yet: https://github.com/aspnet/DotNetTools/issues/151. You might want to consider separate npm scripts for running with and without the watch. Also, the MVC routing is only required when someone links directly to a route from another site. If you are already running the single page application (at localhost:3000 for instance), the router is aware of any actions that require client-side routing. When someone navigates directly to localhost:3000/about from another web site, the server receives the request first because the client application hasn’t been loaded yet. This is why MVC simply takes any route and returns the client application. Once the client application loads – the client router takes over again. You can get more sophisticated than this as Darin points out but that’s the fundamental scenario this tutorial addresses. Hope that helps.

      1. Thank you for the explanation. I can careless about webapi at this moment. What I’d like to see is to utilize VSCode launch configuration to start a web application and launch a browser with debugging capability. I am pretty sure it is possible and it would not be a big deal for VSCode team. But this is vital for new angular 2 developers. Tremendous amount of efforts have been wasted to figure out how to configure and use the tool instead of learning and applying Application Framework. It would be great if you could extend this tutorial with a combined build-deploy-launch-debug operation. Thank you in advance.

  4. This series has to be one of the best tutorials I have read in a very long time. Thank you so very much!

    If you don’t mind taking fan requests, I would love to see a tutorial on how to use Angular2 with ASP.NET’s authentication and authorization.

    Thanks again!

  5. Excellent article series. Even though there are templates out there to automatically scaffold .NET Core and Angular2 they hide too much behind the scenes ‘magic’. Far more beneficial for the developer to learn from the ground up so that errors or changes required to scaffolding can be understood correctly.

  6. Relatively new to Webpack. Any guidance in how to use razor views for Template URL’s in the angular 2 components would be much appreciated. The latest VS 2017 RC4 Angular 2 ASP.NET Core template pack will not allow me to do this. I have tried all manners of regular expression and file path based excludes in the webpack dev configuration rules section (by Ang 2 components using partial views for template URL’s are in ClientApp/app/components/razor sub-folder but no luck 🙁

    module: {
    rules: [
    {
    test: /\.ts$/,
    include: [/ClientApp/],
    use: [‘awesome-typescript-loader?silent=true’]
    },
    {
    test: /\.ts$/,
    include: [/ClientApp/],
    exclude: [/razor/],
    use: [‘angular2-template-loader’]
    },
    { test: /\.html$/, use: ‘html-loader?minimize=false’ },
    { test: /\.css$/, use: [‘to-string-loader’, ‘css-loader’] },
    { test: /\.(png|jpg|jpeg|gif|svg)$/, use: ‘url-loader?limit=25000’ }
    ]
    },

    1. In general, I’d recommend using either Angular templates (from static HTML files) OR Razor views (.cshtml) – but not both for the same part of the UI. What are you trying to accomplish by using the Razor template in this way?

      1. Leverage server side techniques like tag helpers in ASP.NET core to inject client side validations by using server side EF model attributes/annotations thus having validations in one place as attributes on the server side EF model. I can definitely live without the Razor Views, it was just an added bonus. Also as needs grow having a server side rendering technology could be a big help. Thanks again though for taking the time to answer!

        1. EF model annotations – interesting. You got me thinking if/how they could be integrated with Angular form validation – hmm. 🙂

  7. Thank you very much. This is a tutorial worth working through.

    Had to change Microsoft.NETCore.App and Microsoft.AspNetCore.StaticFiles to version 1.0.0 – other than that it all worked great.

    Thank you again.

  8. Many thanks your tutorial and it is the best Angular 2 with dotnet core tutorial I have found. You have clarified so many basic stuff that other tutorial just omitted.

  9. Great tutorial! It helped me using VS Code with confidence.

    Just a suggestion, it would be helpful to mention that the ordering of `ConfigureServices` & `Configure`methods are important. I was stuck for a while when mine didn’t compile.

Leave a Reply

Your email address will not be published. Required fields are marked *