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!