This is the first in a series of three articles on building CRUD applications in Angular:

Many business application Web developers build Create, Read, Update, Delete (CRUD) pages in their day-to-day jobs. Over the years, you might have built these pages in Classic ASP, Web Forms, and MVC. You might have even used JavaScript, jQuery and possibly AngularJS. It's now, once again, time to learn how to build a CRUD page using Angular 4. In this series of articles, you'll start with an empty Web application, built using Visual Studio 2015, and build up to a complete CRUD page.

In this first part, you're going to create a new Web project, add the appropriate Angular files, and display a simple page. You're then going to create a Product table, build an Entity Framework data layer to retrieve data from that table, and create a Web API to call from Angular. You'll finish by displaying a list of products on an HTML page by calling the Web API. You can see the finished HTML page by looking at Figure 11.

Set Up Your Computer

Before you begin to create an Angular application, you must prepare your development computer. There are two tools required: Node.js and Node Package Manager. In addition, you must configure Visual Studio 2015 to use TypeScript. You should also get the Angular QuickStart project at https://github.com/angular/quickstart.

Install Node

If you haven't done so already, download and install Node.js and NodeJS Package Manager (npm). You can get these two packages at https://nodejs.org/en/download/. Follow the instructions for downloading and installing these tools on nodejs.org.

Configure Visual Studio 2015

Most developers use TypeScript for Angular development. Ensure that you've downloaded and installed TypeScript 2.x for Visual Studio 2015. Select the Tools > Extensions and Updates menu to bring up the Extensions and Updates window (Figure 1) for Visual Studio. Click on the Installed node in the tree view and look through your list to see if you've installed TypeScript 2.x. If you have an older version of TypeScript, you need to update it.

Figure 1: Use the Extensions and Updates window to check for TypeScript.
Figure 1: Use the Extensions and Updates window to check for TypeScript.

To update to TypeScript 2.x, click on the Online node in the tree view and perform a search for TypeScript. Locate the latest version of TypeScript and download and install it into Visual Studio.

One last configuration item for Visual Studio is to select the Tools > Options menu and expand the Projects and Solutions node. Click on the External Web Tools (Figure 2). Ensure that the $(PATH) variable is located above any $(DevEnvDir) variables, if they exist, in your environment. If you don't move this up, Visual Studio attempts to use its own tools instead of npm to update packages. In my installation of Visual Studio, these variables didn't exist.

Figure 2: Make sure that the $(PATH) variable is located high in your external tools.
Figure 2: Make sure that the $(PATH) variable is located high in your external tools.

Install Angular Quick Start

Now that you've configured Visual Studio, you're going to need some configuration files and some TypeScript files to make it easier to get started with Angular. The Angular team has created a sample project that contains these files. This sample project is not a Visual Studio project, so you're only going to use some of the files from this project and not bring all of them into your project. Download the quick start zip file from https://github.com/angular/quickstart. After the zip file has downloaded, unzip the files into a folder on your hard drive.

Create Web Project for Angular

With your computer now appropriately configured, let's walk through the steps of creating and running your first Angular application. This ensures that your environment is configured correctly prior to trying to add the Web API and make HTTP calls.

Create a New Web Project

Start Visual Studio 2015 and select File > New > Project… from the menu. From the tree-view in the New Project window (Figure 3), expand Templates > Visual C# > Web. Click on the Web node and from the list of templates, select the ASP.NET Web Application (.NET Framework). Set the Name of the new project to ProductApp. Click the OK button.

Figure 3: Select an ASP.NET Web Application from the New Project window.
Figure 3: Select an ASP.NET Web Application from the New Project window.

After clicking the OK button, you're taken to a screen (Figure 4) that shows you a list of ASP.NET 4.5.2 Templates. Select the Empty Web project. You're going to use the Web API to retrieve data from a SQL Server table, so check the Web API check box. Click the OK button and Visual Studio creates your new project.

Figure 4: Select the Empty Web project and choose the Web API option.
Figure 4: Select the Empty Web project and choose the Web API option.

Add Angular Files

Using Windows Explorer, navigate to the QuickStart files you downloaded earlier. Do NOT copy all of these files into your Visual Studio project because you don't need all of them. Locate and copy the following files into the root of your new Visual Studio project.

  • \bs-config.json
  • \package.json
  • \tslint.json

Copy the whole folder \src into the root folder of your VS project.

Expand the \src folder and select the \app folder and all files within the folder. Right-mouse click and select the Include in Project menu. When you're prompted to include the TypeScript typings, just answer No.

Restore Packages

Even though you've added an HTML page and some TypeScript files, nothing's going to work yet. You need to download a whole new folder using the package manager utility (npm). Visual Studio can download all of these files using npm, but you must first close and reopen Visual Studio. If you don't close and reopen Visual Studio, the appropriate menu item won't show up. Go ahead and close your instance of Visual Studio now, and then reopen the ProductApp project. Right-mouse click on the package.json and select Restore Packages from the menu (Figure 5). This process takes a little while, so be patient.

Figure 5: The Restore Packages menu only shows up after closing and reopening Visual Studio.
Figure 5: The Restore Packages menu only shows up after closing and reopening Visual Studio.

After the installation of the packages is complete, click on the Show All Files icon at the top of the Solution Explorer window. A new folder has appeared named node_modules (Figure 6). DO NOT include this folder in your project. You don't need all of these files. They're just there to support the various libraries you use when developing with Angular.

Figure 6: Don't include the node_modules folder in your project.
Figure 6: Don't include the node_modules folder in your project.

At this point, you're ready to test and see if you've hooked up everything correctly. Press F5, and if you have everything correct, you'll see a page displayed in your browser that looks like Figure 7.

Figure 7: If you get to this page, your Angular installation is working.
Figure 7: If you get to this page, your Angular installation is working.

Add Bootstrap

To give your application a more professional appearance, add Twitter's Bootstrap using the NuGet Package Manager. Right-mouse click on the ProductApp project and select Manage NuGet Packages… from the menu. Click on the Browse tab and search for “bootstrap.” Click the Install button to have Visual Studio download and add the appropriate files to your ProductApp project. Add the appropriate <link> and <script> tags to the index.html files by dragging and dropping them from your project folders into the <head> element. Be sure to put them in the following order.

<link href="Content/bootstrap.min.css" rel="stylesheet" />
<script src="Scripts/jquery-1.9.1.min.js">   </script>
<script src="Scripts/bootstrap.min.js">   </script>

Eliminate Hard-Coded HTML Templates

In Figure 7, you saw the words “Hello Angular” appear. These words came from an HTML snippet contained in the app.component.ts file. I don't like having HTML written in TypeScript, so I create an HTML page to display the HTML for the landing page of my Angular application.

Right-mouse click on the \app folder and select Add > HTML Page and set the name to app.component.html. Click the OK button. Remove the HTML in the page and replace it with the following:

<div class="row">
  <div class="col-xs-12">
    <h1>{{pageTitle}}</h1>
  </div>
</div>

In the HTML above, the value for a variable named pageTitle is going to be retrieved from the AppComponent class and displayed within an <h1> element. Open the \app\app.component.ts file and you'll see the following declaration.

@Component({
    selector: 'my-app', template: `<h1>Hello {{name}}</h1>`
})

Now that you've created the new HTML page, you're going to remove the hard-coded HTML from the template property in the @Component decorator. Note that the template property uses the back-ticks and not apostrophes for creating the hard-coded HTML template. If you wish to use hard-coded templates like this, you need to use the back-tick. I have found, however, that if you have any more than a few lines of HTML, this property becomes cumbersome to maintain. Let's change the template property to the templateUrl property and set that property to the string 'app.component.html'. Once you use a file name, you switch to using apostrophes.

@Component({
    moduleId: module.id,
    selector: 'my-app', 
    templateUrl: './app.component.html'
})

Another change you're making to this @Component decorator is adding the property moduleId and setting that property to the value module.id. This property helps Angular understand relative paths in relation to the current component. If you didn't use the moduleId property, you change the templateUrl property to './app/app.component.html'. I prefer to use relative paths, as opposed to having to fully qualify the path in relation to the root.

The AppComponent class that came with the QuickStart sample has a name property set to the word ‘Angular’. The next snippet is the code in the app.component.ts file.

export class AppComponent {
    name = 'Angular';
}

In the app.component.html page you created, you changed the HTML to bind to a property called pageTitle. Remove the name property and add the pageTitle property, as shown in the following code snippet.

export class AppComponent
{
    pageTitle = 'Product Application;
}

The last thing to do is to modify the index.html to use the Bootstrap CSS. Add a <div> element immediately after the <body> element. Set the CSS class equal to “container”. All other HTML pages in your Angular application are going to be loaded within the directive, so you want them all to live within a Bootstrap container style.

<body>
    <div class="container">
        <my-app>
           <p>Loading application...</p>
        </my-app>
    </div>
</body>

If you run the application again, you'll see the same page as before but now with the text Product Application.

How All of the Pieces Fit Together

Now that you have your Angular application up and running, let's look at the various files you added and how they relate to one another. In Figure 8, you can see an overview of all the HTML and TypeScript files you've worked with thus far.

Figure 8: How Angular files relate to one another
Figure 8: How Angular files relate to one another

The following numbers are a description of each of the numbers contained in Figure 8.

  1. The application runs index.html, which runs the JavaScript System.import() function to load and run the code within the \app\main.ts file.
  2. The code in main.ts imports the code from the app.module.ts file. The AppModule class, defined in app.module.ts, is used as the “bootstrap” code to start the Angular application.
  3. The AppModule class is empty, but what's important is the @NgModule decorator to define various metadata for the module. The one you?re concerned with right now is the declarations property, which identifies AppComponent class as the root component for the Angular application. The AppComponent class is defined in the app.component.ts file. The app.component.ts file is imported at the top of the app.module.ts file.
  4. The AppComponent class has a @Component decorator that sets the selector property with a name of my-app. This selector is used as the HTML directive <my-app> on the index.html page. The my-app directive is associated with the AppComponent component, so it knows to look at the templateUrl property to identify the HTML page it needs to insert into the location of the <my-app> directive.
  5. The HTML in the app.component.html page is read and inserted into the index.html where the <my-app> directive is located.

Prepare the Server-Side Code

Now that you have your client-side Angular application working, create a SQL Server table and code for your server-side. On the server-side, you need to create a table to hold product data, an Entity Framework data layer, and a Web API. In addition, you need to add some code in the Global.asax file to change the case of the C# property names to be camel-case to more closely match JavaScript coding standards.

Create Product Table

To create the page that lists product data (Figure 11), create a table called Product in a SQL Server database. In the following code snippet, you can see the full definition of the Product table.

CREATE TABLE Product (
    ProductId int NOT NULL
                  IDENTITY(1,1)
                  PRIMARY KEY NONCLUSTERED,
        ProductName varchar(150) NOT NULL,
        IntroductionDate datetime NOT NULL,
        Url varchar(255) NOT NULL,
        Price money NOT NULL
)

Once you create the table, add some data to each field. Create enough rows that you can see several rows on your final HTML page. In Figure 9, you can see the data that I used to create the list of product data for this sample application.

Figure 9: Fill in the Product table with some data to display on your HTML page.
Figure 9: Fill in the Product table with some data to display on your HTML page.

Build Entity Framework Model

With your Product table in place, you need a data layer to be able to retrieve and modify the data in that table. Let's use the Entity Framework code generator that's built-into Visual Studio to create this data layer. This code generator allows you to list, add, edit and delete data from the Product table.

Right-mouse click on the \Model folder and select Add > New Item… Click on the Data node and then select ADO.NET Entity Data Model from the list of templates. When prompted for the item name, type in ProductDB. Click the Add button to move to the next step in this process.

Select Code First from database and click the Next button to create a server connection. Create a new connection to the database where you installed the Product table. After creating the connection, leave everything else the same in this step of the wizard and click the Next button.

Expand the tree view and locate the Product table you previously created. Click the Finish button to have Visual Studio create the Entity Framework data model for the Product table.

After the Entity Framework generates the classes, you'll have an entity class called Product that has the properties listed in the next snippet. I'm showing this class to you because you're going to create a corresponding Angular Product class in TypeScript later in this article. Don't add this class; it's already there in the \Model folder.

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public DateTime IntroductionDate { get; set; }
    public string Url { get; set; }
    public decimal Price { get; set; }
}

Create Web API

To retrieve data from your Product table from within an Angular application or any client-side technology, you must create a Web API. Right-mouse click on the \Controllers folder and select Add > Web API Controller Class (v2.1) from the menu. Set the name to ProductController and click the OK button. Add a Using statement at the top of this controller file to include the namespace generated by the Entity Framework.

using ProductApp.Models;

Delete all of the code within this new controller class, as you're going to write your Web API methods using a more updated approach than what's generated by Visual Studio. Type in the code listed in the code snippet below to create a method that retrieves all products from the Product table.

public IHttpActionResult Get() {
    IHttpActionResult ret;
    ProductDB db = new ProductDB();
    if (db.Products.Count() > 0) {
        ret = Ok(db.Products);
    }
    else {
        ret = NotFound();
    }
    return ret;
}

Fix the Global.asax

Earlier, I mentioned that the C# class Product is going to have a similar class created on the client-side that maps all properties one-to-one. However, the C# class uses pascal-casing of properties, and the TypeScript class is going to use camel-casing. You can add code to the Application_Start method in the Global.asax file to automatically convert properties from pascal-case to camel-case. Open the Global.asax file and at the top of the file add the two using statements shown in this code snippet.

using Newtonsoft.Json.Serialization;
using System.Net.Http.Formatting;

Modify the Application_Start method to look like the code shown in Listing 1. The code you're adding to the Application_Start method selects the current GlobalConfiguration.Configuration property and assigns it a variable called config. The next line of code instructs the formatter to ignore any self-referencing objects in the Entity Framework. There aren't any in this application, but this code can be useful when the Entity Framework generates more tables that have references to one another.

Listing 1: Modify the Global.asax to automatically convert the casing of properties.

protected void Application_Start() {
    GlobalConfiguration.Configure(WebApiConfig.Register);

    // Get Global Configuration
    HttpConfiguration config = GlobalConfiguration.Configuration;

    // Handle self-referencing in Entity Framework
    config.Formatters.JsonFormatter
        .SerializerSettings.ReferenceLoopHandling =
        Newtonsoft.Json.ReferenceLoopHandling.Ignore;

    // Convert to camelCase
    var jsonFormatter = config.Formatters
        .OfType<JsonMediaTypeFormatter>()
        .FirstOrDefault();

    jsonFormatter.SerializerSettings.ContractResolver = new
        CamelCasePropertyNamesContractResolver();
}

The next line of code queries the Formatters collection and retrieves the first instance of any JsonMediaTypeFormatter object it finds. Finally, you create a new instance of a CamelCasePropertyNamesContractResolver and assign that to the formatter's ContractResolver property. This property controls how the JSON objects are formatted and sent to the client-side caller.

Build Client-Side Product List

Now that you have your server-side pieces in place and have gotten Angular to work in your project, you can start building the HTML and classes to create a list of products. You're going to create a folder under the \app folder for each of your different pages. In this sample, you're building a system for handling products, so you're going to create a \product folder. If you add more pages to handle customers, you create a \customer folder, and so on. This helps you keep all of your different pages organized. In this part of the article, there are four new files that you're going to create. These four files are summarized in Table 1 (see end of this article online).

Create Product Class

As you're going to retrieve a collection of Product classes from the Web API, you need a Product class written in TypeScript. Add a new folder under the \app folder named \product. Right-mouse click on this new folder and select Add > TypeScript file from the context-sensitive menu. Give this new file the name of product.ts. Add the following code in this file.

export class Product {
    productId: number;
    productName: string;
    introductionDate: Date;
    price: number;
    url: string;
    categoryId: number;
}

Notice that the structure of this class is exactly like the Product class that the Entity Framework generated. The only difference is the use of camel-case instead of pascal-case. Remember that you added code in the Global.asax to handle this conversion for you.

Create Product Service

You need an Angular product service to call the Web API controller that you created earlier. This product service retrieves all products. Right-mouse click on the \app\products folder and select New > TypeScript file from the menu. Set the name to product.service.ts and click the OK button. Add the code shown in Listing 2.

Listing 2: The product service retrieves a collection of products from your Web API.

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/throw';

import { Product } from "./product";

@Injectable()
export class ProductService {
    private url = "api/product";

    constructor(private http: Http) {
    }

    getProducts(): Observable<Product[]> {
        return this.http.get(this.url)
              .map(this.extractData)
              .catch(this.handleError);
    }

    private extractData(res: Response) {
        let body = res.json();
        return body || {};
    }

    private handleError(error: any): Observable<any> {
        let errors: string[] = [];

        switch (error.status) {
            case 404: // Not Found
            errors.push("No Product Data Is Available.");
            break;

            case 500: // Internal Error
            errors.push(error.json().exceptionMessage);
            break;

            default:
            errors.push("Status: " + error.status
                         + " - Error Message: "
                         + error.statusText);
            break;
        };

        console.error('An error occurred', errors);

        return Observable.throw(errors);
    }
}

Let's break down the code in Listing 2. You first import the various classes and functions you need for this service using the import keyword.

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { Product } from "./product";

The Injectable class is needed to support the use of the @Injectable decorator attached to the ProductService class. This decorator informs Angular that this class may be injected into the constructor of any component. The HTTP and Response classes are used to access the Web API and to retrieve the response from the Web API. Next, is the Observable import. You're going to retrieve data using the Angular HTTP service. This service returns all calls from a Web API as an Observable object. Next are a series of imports that provide extension functions such as map, catch, and throw.

These functions, as well as the Observable object, are supplied by RxJS or Reactive Extensions for JavaScript. You can learn more about these libraries at https://github.com/Reactive-Extensions/RxJS. The RxJS library helps you transform, compose, and query any type of data. In the ProductService class, you use it to take the result set of data you retrieve from the Web API and map it to an Angular Observable object.

The last import is the Product class you created. Because you want to return an array of product objects returned from the Web API, you must import this class.

Angular injects the HTTP service into the ProductService class. Any class marked with the @Injectable decorator may be injected into any class by declaring it in the constructor. The HTTP service class, created by the Angular team, has been decorated with the @Injectable decorator.

constructor(private http: Http) {
}

The next two functions in the class are getProducts and extractData. The getProducts function calls the Web API using the get function of the HTTP service. It creates a promise using the toPromise extension function. If data is retrieved, the then function is called and passed a reference to the extractData function. You don't really need the extractData function, but I like having this function as it makes it easy for to set a breakpoint and see the data that's been returned. The extractData function is passed in the HTTP response object. Call the JSON function on this response object to retrieve the array of product data.

getProducts(): Observable<Product[]> {
    return this.http.get(this.url)
                .map(this.extractData)
                .catch(this.handleError);
}
private extractData(res: Response) {
    let body = res.json();
    return body || {};
}

The last function in this class is handleError. This function is used to publish the error information and return an Observable object with a string array of the errors returned from the call. I'm going to also output the error(s) to the console window of the browser. Use a switch/case statement so you can supply different error messages based on what's returned from the Web API call. Inform the calling code of the error by passing the errors array to the throw function on the Observable class.

Update the app.module.ts File

In order to inject the HTTP service into the ProductService class, you need to import the HttpModule in the app.module.ts file. You then add this imported module to the @NgModule imports property. This makes it available for use in any of your classes. Angular takes care of injecting this service whenever it sees a reference to HTTP, as you saw in the constructor of your ProductService class.

import { HttpModule } from '@angular/http';
@NgModule({
    imports: [BrowserModule, HttpModule ]
    ...
})

Remember that you marked your ProductService as an @Injectable class. This means you want Angular to automatically inject this service into any of your class constructors. To accomplish this, you also need to import the ProductService in your app.module.ts file and add this class to the providers property.

import { ProductService }
  from "./product/product.service";
@NgModule({
    imports: [BrowserModule, HttpModule ],
    declarations: [ AppComponent ],
    bootstrap: [AppComponent],
    providers: [ProductService]
})

Add the Product List HTML Page

It's now finally time to build the components needed to display a list of products like the page shown in Figure 11. Add a new HTML page named product-list.component.html to the \app\product folder. Remove all of the HTML in the new page and enter the HTML shown in Listing 3.

Listing 3: Build the HTML table using the Angular *ngFor directive.

<div class="row" *ngIf="products && products.length">
  <div class="col-xs-12">
    <div class="table-responsive">
      <table class="table table-bordered table-condensed table-striped">
        <thead>
          <tr>
            <td>Product Name</td>
            <td>Introduction Date</td>
            <td>URL</td>
            <td class="text-right">Price</td>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let product of products">
            <td>{{product.productName}}</td>
            <td>{{product.introductionDate | date }}
            </td>
            <td>{{product.url}}</td>
            <td class="text-right">
              {{product.price | currency:'USD':true }}
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

<div class="row" *ngIf="messages && messages.length == 0">
  <div class="col-xs-12">
    <div class="alert alert-warning">
      <ul>
        <li *ngFor="let msg of messages">{{msg}}</li>
      </ul>
    </div>
  </div>
</div>

In the HTML that builds the product table, use the *ngIf directive to test to see if there are any products. If there are, you build an HTML table using the *ngFor directive to iterate over a collection named products in the ProductListComponent class. Each time through the loop, a product object is assigned to a local variable named product. Use this product variable to retrieve each property of the Product class and display the value of each in each cell of the table.

At the end of the HTML, there's another <div> element that displays any messages in an unordered list. You may receive errors if the call to the Web API failed for some reason, or maybe there was no data in the table. In that case, the messages property of the ProductListComponent class is filled in with an array of string messages to display.

Create the Product List Component

From the HTML in Listing 3, you know that your ProductListComponent class (Listing 4) must expose two properties to the HTML page products and messages. This class must be injected with the ProductService class from which you can request the product data. This class must also have some exception handling in case the Web API throws an exception or doesn't return data. Create the ProductListComponent class by adding a new TypeScript file under the \app\product folder named product-list.component.ts. Write the code contained in Listing 4.

Listing 4: The product listing component.

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

import { Product } from "./product";
import { ProductService } from "./product.service";

@Component({
    moduleId: module.id,
    templateUrl: "./product-list.component.html"
})
export class ProductListComponent implements OnInit {
    constructor(private productService: ProductService) {
    }

    ngOnInit(): void {
        this.getProducts();
    }

    // Public properties
    products: Product[] = [];
    messages: string = [];

    private getProducts(): void {
        this.productService.getProducts()
            .subscribe(products => this.products = products,
                         errors => this.handleError(errors));
    }

    private handleErrors(errors: any): void {
        for (let msg of errors) {
            this.messages.push(msg);
        }
    }
}

Let's look at each piece of the ProductListComponent class, and learn why you need each line of code and what each does. The first step is to import the various classes you use within this class. The following four imports are used in this class.

import { Component } from "@angular/core";
import { OnInit } from '@angular/core';
import { Product } from "./product";
import { ProductService }
  from "./product.service";

You can see the Component import is needed for the @Component decorator. The templateUrl property in the decorator points to the product-list.component.html page that you just created.

The OnInit import is an abstract class and isn't absolutely necessary, but I like to use it to ensure that I type in the correct function name: ngOnInit. The ngOnInit function is a lifecycle event raised when the Angular engine creates an instance of this class. Use the ngOnInit function to call the getProducts function to retrieve the product data from your product service class.

The last two imports are easy to understand. You need the Product class because you're going to fill an array of Product objects from your call to the product service. The constructor for this class receives the ProductService class from the Angular injection and assigns that instance to the private property named productService.

constructor(private productService: ProductService) {
}

Two properties are declared in this class: products and messages. The products property is an array of Product objects. Initialize this property to an empty array by using the = []; syntax after the declaration of this property. The messages property is also a string array used to display any error messages on the page.

// Public properties
products: Product[] = [];
messages: string[] = [];

The getProducts function calls the getProducts function from the ProductService class. This function returns an Observable object, so you can use the subscribe function to retrieve the product array and assign it to the products property in this class. If an error occurs when calling the Web API, the second parameter to subscribe is called. Take the error object passed back from the ProductService and pass that to the handleError function in this class.

getProducts(): void {
    this.productService.getProducts()
        .subscribe(
            products => this.products = products,
              errors => this.handleError(errors)
        );
}

The handleError function is passed into the array of errors from the call to the product service. This function loops through the errors array, extracts each message and pushes it onto the messages array. Because this messages property is bound to the unordered list on the product-list.component.html page, the error messages are displayed within that list.

private handleErrors(error: any): void {
    for (let msg of errors) {
        this.messages.push(msg);
    } 
}

Update the app.module.ts File

Just like you did with the other components that you added to the project, add the ProductListComponent class to the app.module.ts. Open the app.module.ts file and add an import near the top of the file that looks like the following:

import { ProductListComponent }
  from "./product/product-list.component";

In the @NgModule decorator, add the ProductListComponent class to the declarations property by adding a comma after AppComponent and typing in the name of this class as shown below:

declarations: [ AppComponent, ProductListComponent ]

Angular Routing

No Angular application would be complete without routing. Routing allows you to navigate from one page to another. The app.component.html page you created earlier is the main page for getting to other pages in this application. You're going to add a <router-outlet> directive to this page. Add the following HTML below the other HTML on this component.

<div class="row">
  <div class="col-xs-12">
    <nav>
      <a class="btn btn-primary" routerLink="/productList">
        Product List</a>
    </nav>
    <br />
    <router-outlet></router-outlet>
  </div>
</div>

The key pieces in the HTML above are the routerLink attribute in the anchor tag and the <router-outlet> directive. The <router-outlet> directive tells Angular where to insert the HTML supplied by the component that's routed to by the value in the routerLink attribute. It's the value in this attribute, productList, that you're going to match up with a route defined in a TypeScript file called app-routing.module.ts. Add a new TypeScript file called app-routing-module.ts and add the code shown in Listing 5.

Listing 5: The routing class defines one or more routes for your application.

import { NgModule } from '@angular/core';
import { RouterModule, Routes }
  from '@angular/router';

import { ProductListComponent }
  from "./product/product-list.component";

const routes: Routes = [
    {
        path: 'productList',
        component: ProductListComponent
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

The important part of the app-routing.module.ts file is the constant named routes. This constant contains an array of Route objects. Each object has a path property that's used in the routerLink attribute you created earlier. If the path matches the address in the browser, the component property is used to instantiate an instance of the object listed in this property. Once this class is instantiated, the HTML defined in that class' templateUrl property is inserted into the location where the <router-outlet> directive is located.

The route's constant is added to the singleton instance of the Router service using the forRoot function. You can see this in the imports property of the @NgModule decorator. You can add as many routes to the routes constant as you need for your application.

Overview of Angular Routing

With all that description above, I thought a graphical representation of the Angular routing you're using in this application might help clear things up a little. Here's a description of each of the numbers you see in Figure 10.

  1. Any time the browser's address bar is updated via an anchor tag or other mechanism, the last part of the address is matched up to one of the routes added to the singleton instance of the Router service.
  2. Once Angular finds the path in the Router service, Angular instantiates the class listed in the component property.
  3. After the class is instantiated, the templateUrl property is read from the @Component decorator.
  4. The HTML from the file listed in the templateUrl property is loaded and inserted into the DOM at the location of the <router-outlet> directive.
Figure 10: An overview of the Angular routing process
Figure 10: An overview of the Angular routing process

Update the app.module.ts File

Declare your intention to use routing by importing it into the app.module.ts file. Open the app.module.ts file and add the following import statement near the top of the file.

import { AppRoutingModule }
  from './app-routing.module';

Add the AppRoutingModule to the imports property in the @NgModule decorator. The imports property should now look like the following code snippet.

imports:[BrowserModule, HttpModule, AppRoutingModule]

Run the Page

At this point, you can run the page and see a list of the product data you entered into your Product table. If you entered the same data that I did, you should see a page that looks like Figure 11.

Figure 11: The finished list of products page
Figure 11: The finished list of products page

Summary

Congratulations! You've successfully created your first Angular application using Visual Studio. There are a lot of steps to get Angular up and running, but once you get the basics configured, adding new pages and new components is quite easy because you use the same pattern that you learned in this article. In my next article, you're going to add a detail page, select a single product from a Web API call, and then learn to add, edit, and delete a product.

Table 1: A list of client-side files for the product listing page

product.tsA class that represents a single `Product` object
product.service.tsA class to call the Web API to retrieve product data and return an array of `Product` objects
product-list.component.htmlThe HTML needed to display the table of product data
product-list.component.tsThe class with properties and methods to display the product data