[dotnet new] Angular Single Page Application – Creating a UI for My web-api Project

1489 Header image

Today’s header image was created by João Silas, the original source for the image is available here

Angular2 SPAs and Templates

Last week I did a deep dive into how the Angular2 SPA template worked, including looking at some of the features that the new Microsoft.AspNetCore.SpaServices namespace gives us.

I would give it a read, if you’re at all interested in what really cool features we now have access to via the SpaServices namespace. Here is a direct link to it

This week, we’re going to use that template to build a front end application for the web-api project that we built together in the web-api tutorial series.

you can find the whole series here

We’ll use a published version of that app (although you can use any app that you want) as a server of sorts, sending it GET requests, parsing the responses into a view model and getting Angular to update the relevant views for our SPA with the parsed data using two way binding.

I’ve already uploaded the source code to GitHub, so if you wanted to skip the blog post and go straight to the code, you can do that here.

Or you can click here for a demo of the project we’re about to build

Prerequisites

As with the previous post on the SPA templates, you’ll need to have .NET Core 1.0.1 or greater, node.js and the SPA templates installed on you development machine.

The next section contains a check for the SPA templates and what to do if you don’t have them installed

Other than that, you’ll need some kind of web service to talk to. I’m going to use the published version of dwCheckApi, the address of which is: http://dwcheckapi.azurewebsites.net/. You’re welcome to use this service for development and for following along with this post, if you’d like. The only caveat is that it’s hosted on a free Azure service, so it might be slow to respond from time to time.

dotnet new

Creating a new .NET Core project using the SPA template is pretty simple. However, at the time of publishing this blog post, there is no way to access these templates from the New Project wizards in any of the Visual Studio IDEs.

I’m referring to Visual Studio, Visual Studio for Mac, Xamarin Studio, and Visual Studio Code.

Using the terminal, create a directory for the project, change into it then run the following command:

dotnet new angular

If you receive a response which looks like this:

No templates matched the input template name: [angular]
Template Instantiation Commands for .NET Core CLI.
Usage: dotnet new [arguments] [options]
Arguments:
template The template to instantiate.
Options:
-l|--list List templates containing the specified name.
-lang|--language Specifies the language of the template to create
-n|--name The name for the output being created. If no name is specified, the name of the current directory is used.
-o|--output Location to place the generated output.
-h|--help Displays help for this command.
-all|--show-all Shows all templates
Templates Short Name Language Tags
----------------------------------------------------------------------------------------------------------
Console Application console [C#], F# Common/Console
Class library classlib [C#], F# Common/Library
Unit Test Project mstest [C#], F# Test/MSTest
xUnit Test Project xunit [C#], F# Test/xUnit
ASP.NET Core Empty web [C#] Web/Empty
ASP.NET Core Web App mvc [C#], F# Web/MVC
ASP.NET Core Web API webapi [C#] Web/WebAPI
Solution File sln Solution
Examples:
dotnet new mvc --auth None --framework netcoreapp1.1
dotnet new react
dotnet new --help

then you’ll need to install the SPA templates before continuing.

Take a look at last week’s post to learn how to do this

However, if you receive the something like this, then everything went ok:

Content generation time: 613.5656 ms
The template "MVC ASP.NET Core with Angular" created successfully.

And a quick ls (if you’re on a Unix or Linux machine) or dir (if you’re on a Windows machine) will show the contents of the new project:

ClientApp package.json
Controllers test.csproj
Program.cs tsconfig.json
Startup.cs web.config
Views webpack.config.js
appsettings.json webpack.config.vendor.js
global.json wwwroot

We now need to restore all packages for this project. Our new project uses .NET Core (obviously) and node packages (which is one of the reasons why we needed to install node), so we’ll need to run the following two commands:

dotnet restore
npm install -d

Once the packages have been restored, we’re finally ready to run the application. It’s always good to test that we’ve set everything up correctly before we start developing.

dotnet run

Once the application is running correctly, heading over to the address that .NET Core gives you (mine was localhost:5000) should show something similar to this:

Angular2 SPA Template default view

Adding A Component

Now that we have the application running, it’s time to open the project with our favourite IDE. I’m going to use VS Code.

you could say that I’m a bit of a fan

If you’re going to use Visual Studio Code, then leaving the application running is an important part to building this project. This is because of the inclusion of the webpack middlewear and hot module replacement, each time we make a change to our client side scripts they will be repackaged and sent to the open browser and swapped out as our application is running. All this without the need to refresh the underlying page, too.

it’s really quite impressive

However, if you’re going to open the project with Visual Studio, Visual Studio for Mac, Xamarin Studio or Project Rider, then I’d recommend closing the application first and running it from within those IDEs.

Taking a look at the directory structure for the client side code (which you’ll find in the ClientApp directory), you’ll see that our client side application code is split into components. The project currently has one component per Angular route and we’ll leave it set up like that for now.

the navmenu component is used on each page to build the navigation menu, thus it is used in the app component. More on that in a moment

Angular2 SPA Template ClientApp Directory

Each component has it’s own directory, and is made up of one or more of the following file types:

  • markup
  • script
  • style sheet

The accepted practise is to name the directory after the component, and to name the files within it the same way, but appending ‘.component’ before the file type.

Angular2 SPA Template navemenu Directory
As an example, here are the contents of the navmenu directory

Create a subdirectory within the components one called ‘books’, within that directory create a file called ‘books.component.ts’ and paste the following code into it:

import { Component } from '@angular/core';
import { Http } from '@angular/http';
@Component({
templateUrl: './books.component.html'
})
export class BooksComponent {
constructor(http: Http) {
this.http = http;
this.loading = false;
this.baseApiUrl = 'http://dwcheckapi.azurewebsites.net/Books/Search?searchString=';
this.books = null;
this.registerFunctions();
}
// private vars
private http: Http;
// public bound vars
success: boolean;
loading: boolean;
baseApiUrl: string;
searchString = '';
books: Book[];
// public functions
getDwBook: () => void;
private registerFunctions() {
this.getDwBook = () => {
var route = `${this.baseApiUrl}${this.searchString}`;
this.loading = true;
this.http.get(route).subscribe((result) => {
var resultJson = result.json() as ResultJson;
if(resultJson.success) {
this.books = new Array<Book>();
result.json().result.forEach(element => {
this.books.push(new Book(element.bookName, element.bookIsbn10,
element.bookIsbn13, element.bookDescription,
element.bookCoverImageUrl));
});
}
this.loading = false;
});
}
}
}
interface ResultJson{
success: boolean;
result: string;
}
class Book {
constructor(bookName: string, bookIsbn10: string, bookIsbn13:
string, bookDescription: string, bookCoverImageUrl: string){
this.bookName = bookName;
this.bookIsbn10 = bookIsbn10;
this.bookIsbn13 = bookIsbn13;
this.bookDescription = bookDescription;
this.bookCoverImageUrl = bookCoverImageUrl;
}
bookName: string;
bookIsbn10: string;
bookIsbn13: string;
bookDescription: string;
bookCoverImageUrl: string;
}

Then create a file in the same directory called ‘books.component.html’ and paste the following code into it:

<h1>Book Search</h1>
<div class="row">
<div class="col-xs-12">
<input [value]="searchString" (input)="searchString = $event.target.value"/>
<button (click)="getDwBook()">Search</button>
</div>
</div>
<div class="loader" *ngIf="loading">
<p>Searching, please wait</p>
</div>
<div class="table-responsive" *ngIf="!loading && books">
<table class='table'>
<thead>
<tr>
<th colspan="2">Cover</th>
<th colspan="2">Name</th>
<th>ISBN 10</th>
<th>ISBN 13</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let book of books">
<td colspan="2"><img src="{{book.bookCoverImageUrl}}" class="img-responsive"/></td>
<td colspan="2">{{ book.bookName }}</td>
<td>{{ book.bookIsbn10 }}</td>
<td>{{ book.bookIsbn13 }}</td>
<td>{{ book.bookDescription }}</td>
</tr>
</tbody>
</table>
</div>
books.component.ts

The books component TypeScript doesn’t contain code which is too complicated, but let’s take a look at it.

The first thing that we do is import the core and http Angular modules:

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

We need to import the core module to access Angular’s core functionality, without it we wouldn’t be able to add the component meta data.

more on that in a moment

We need to import the http module to gain access to Angular’s http get method – which performs an Ajax get for us. Without this, we’d have to depend on jQuery or write our own XMLHttpRequest code. Since Angular supplies this for us, there’s no point in writing our own.

Then we add the Angular Component meta data:

@Component({
templateUrl: './books.component.html'
})

We’re using the Component decorator to tell Angular that our class will be an Angular component. The Component decorator takes in meta data about our class, and uses that meta data when we access it via markup or routing.

We only need to supply the templateUrl field for this component. This field tells Angular where to find the HTML to add to the DOM when this component is requested.

we’ll see how to do that in a moment

The template file is always external to and relative to the TypeScript. It also uses Unix-like file paths: all directories are separated by forward slashes, and a leading dot or period means “current working directory”.

So our templateUrl value:

templateUrl: './books.component.html'

means:

start in the same directory as this file and find the file named ‘books.component.html’

Skipping over the BooksComponent class for a moment, we then added some TypeScript classes and Interfaces which match the format of the data returned from my service:

interface ResultJson{
success: boolean;
result: string;
}
class Book {
constructor(bookName: string, bookIsbn10: string, bookIsbn13:
string, bookDescription: string, bookCoverImageUrl: string){
this.bookName = bookName;
this.bookIsbn10 = bookIsbn10;
this.bookIsbn13 = bookIsbn13;
this.bookDescription = bookDescription;
this.bookCoverImageUrl = bookCoverImageUrl;
}
bookName: string;
bookIsbn10: string;
bookIsbn13: string;
bookDescription: string;
bookCoverImageUrl: string;
}

This isn’t the best way to add this interface and class to the project, but it will do for now. To see how it should be done, take a look at the ClientApp folder in the GitHub Repo

Everything returned from my service (even if you access ‘/’) is a ResultJson type:

interface ResultJson{
success: boolean;
result: string;
}

This is so that you can be sure whether the query succeeded or failed, before parsing the JSON result text.

Then we’ve defined a class for the returned book entries:

class Book {
constructor(bookName: string, bookIsbn10: string, bookIsbn13:
string, bookDescription: string, bookCoverImageUrl: string){
this.bookName = bookName;
this.bookIsbn10 = bookIsbn10;
this.bookIsbn13 = bookIsbn13;
this.bookDescription = bookDescription;
this.bookCoverImageUrl = bookCoverImageUrl;
}
bookName: string;
bookIsbn10: string;
bookIsbn13: string;
bookDescription: string;
bookCoverImageUrl: string;
}

We could have created a TypeScript Interface for the Book Class, but it was such a simple class (and we’re not using it anywhere else in this demo) that I thought, for this example at least, we didn’t need one.

And then we have the BookComponent class:

export class BooksComponent {
constructor(http: Http) {
this.http = http;
this.loading = false;
this.baseApiUrl = 'http://dwcheckapi.azurewebsites.net/Books/Search?searchString=';
this.books = null;
this.registerFunctions();
}
// private vars
private http: Http;
// public bound vars
success: boolean;
loading: boolean;
baseApiUrl: string;
searchString = '';
books: Book[];
// public functions
getDwBook: () => void;
private registerFunctions() {
this.getDwBook = () => {
var route = `${this.baseApiUrl}${this.searchString}`;
this.loading = true;
this.http.get(route).subscribe((result) => {
var resultJson = result.json() as ResultJson;
if(resultJson.success) {
this.books = new Array<Book>();
result.json().result.forEach(element => {
this.books.push(new Book(element.bookName, element.bookIsbn10,
element.bookIsbn13, element.bookDescription,
element.bookCoverImageUrl));
});
}
this.loading = false;
});
}
}
}

Most of this should be straight forward, so I’ll whiz past the constructor and the variables. The main work is happening in our getDwBook method:

this.getDwBook = () => {
var route = `${this.baseApiUrl}${this.searchString}`;
this.loading = true;
this.http.get(route).subscribe((result) => {
var resultJson = result.json() as ResultJson;
if(resultJson.success) {
this.books = new Array<Book>();
result.json().result.forEach(element => {
this.books.push(new Book(element.bookName, element.bookIsbn10,
element.bookIsbn13, element.bookDescription,
element.bookCoverImageUrl));
});
}
this.loading = false;
});
}

The part that caught me out about Angular’s http get method was that the response doesn’t come back in the same format as was sent from the server. The response variable returned by the http get method is of type “Response”.

You can read about the Response class at the Angular Documentation

As such, when the response comes back we parse the json property of it to a ResultJson object. Then, if the response indicated that it was successful, we parse the result portion of it to an array of Book objects.

This is definitely not the best way to do handle the response from the server, mainly because it’s not strongly typed. But I feel like it’s a good way to get across the idea that the response from Angular’s http get method is not in the format that the server provides it (or what you might expect, if you’ve only ever used jQuery to do XMLHttpRequests).

In a real world application, I would have preferred a method to parse the returned data and deal with it using array.map.

book.component.html

That’s the TypeScript code covered, let’s take a look at the markup:

<h1>Book Search</h1>
<div class="row">
<div class="col-xs-12">
<input [value]="searchString" (input)="searchString = $event.target.value"/>
<button (click)="getDwBook()">Search</button>
</div>
</div>
<div class="loader" *ngIf="loading">
<p>Searching, please wait</p>
</div>
<div class="table-responsive" *ngIf="!loading && books">
<table class='table'>
<thead>
<tr>
<th colspan="2">Cover</th>
<th colspan="2">Name</th>
<th>ISBN 10</th>
<th>ISBN 13</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let book of books">
<td colspan="2"><img src="{{book.bookCoverImageUrl}}" class="img-responsive"/></td>
<td colspan="2">{{ book.bookName }}</td>
<td>{{ book.bookIsbn10 }}</td>
<td>{{ book.bookIsbn13 }}</td>
<td>{{ book.bookDescription }}</td>
</tr>
</tbody>
</table>
</div>

Let’s divide the markup into three sections (ignoring the header element):

  • Search
  • Loading message
  • Results

The search is provided as a single row with two controls:

<div class="row">
<div class="col-xs-12">
<input [value]="searchString" (input)="searchString = $event.target.value"/>
<button (click)="getDwBook()">Search</button>
</div>
</div>

The value of the input box is captured and stored in the searchString variable using the Angular directive:

<input [value]="searchString" (input)="searchString = $event.target.value"/>

As the value of the input box changes, it’s new value is written to the searchString variable.

The search button’s click method is bound to the getDwBook method using the Angular directive:

<button (click)="getDwBook()">Search</button>

The loading message is shown only if the loading variable is set to true:

<div class="loader" *ngIf="loading">
<p>Searching, please wait</p>
</div>

The results are only shown if we’re not loading and there are entries in the books variable.

<div class="table-responsive" *ngIf="!loading && books">
<table class='table'>
<thead>
<tr>
<th colspan="2">Cover</th>
<th colspan="2">Name</th>
<th>ISBN 10</th>
<th>ISBN 13</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let book of books">
<td colspan="2"><img src="{{book.bookCoverImageUrl}}" class="img-responsive"/></td>
<td colspan="2">{{ book.bookName }}</td>
<td>{{ book.bookIsbn10 }}</td>
<td>{{ book.bookIsbn13 }}</td>
<td>{{ book.bookDescription }}</td>
</tr>
</tbody>
</table>

This is why we new it up every time that we perform a search, performance wise this isn’t the best either. But I doubt that the get method will be being performed and returning data at a rate which could cause serious issues.

the webapi application being on free Azure hosting will, no doubt, help towards that.

In a real world application you would clear the array each time, rather than setting it equal to a new instance of an array.

The table body is rendered with a row for each book entry in the Array of books, and the value of each table data element is set using the handlebar notation (which comes directly from the first version of Angular).

<tbody>
<tr *ngFor="let book of books">
<td colspan="2"><img src="{{book.bookCoverImageUrl}}" class="img-responsive"/></td>
<td colspan="2">{{ book.bookName }}</td>
<td>{{ book.bookIsbn10 }}</td>
<td>{{ book.bookIsbn13 }}</td>
<td>{{ book.bookDescription }}</td>
</tr>
</tbody>

app.module.ts

Before we can use the new component, we need to add it to the app.module.ts file, replace the content of the app.module.ts file with the following:

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component';
import { BooksComponent } from './components/books/books.component';
@NgModule({
bootstrap: [ AppComponent ],
declarations: [
AppComponent,
NavMenuComponent,
CounterComponent,
FetchDataComponent,
HomeComponent,
BooksComponent
],
imports: [
UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too.
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'books', component: BooksComponent},
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: '**', redirectTo: 'home' }
])
]
})
export class AppModule {
}

I wrote a description of the app.module.ts file in last week’s post

Click here to jump to that section of last week’s post.

What we’ve done here is imported the BookComponent

import { BooksComponent } from './components/books/books.component';

Added it to the Angular Declarations (in the NgModule directive):

declarations: [
AppComponent,
NavMenuComponent,
CounterComponent,
FetchDataComponent,
HomeComponent,
BooksComponent
],

Then added a route for it:

RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'books', component: BooksComponent},
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: '**', redirectTo: 'home' }
])

Testing Our New Component

Because we’ve added files (not just edited pre-existing ones), we’ll have to stop our .NET Core application with Crtl+C, then tell webpack to do it’s thing:

webpack

This will force webpack to take another look at the directories, rebuild all of the modules, create a new one for our BooksComponent and bundle them for us. Then we need to run the application again:

dotnet run

I had to do this because I was using Visual Studio Code. If you are using an IDE, then webpack should automatically repackage everything for you

Once the application has booted again, if you head over to the URL that .NET Core has given you (for me it was localhost:5000), then change the url to end in ‘/books’ rather than ‘/home’ then you should see something similar to this:

Angular2 SPA Template Books view

And if you perform a search:

Angular2 SPA Template Rince Search in progress

You will get a loading message until a response is received. And when we get a response:

Angular2 SPA Template Rince Search complete

The results will be parsed and the images will be lazy loaded in:

Angular2 SPA Template Rince books lazy loaded

navmenu

The last thing that we need to do, in order to make this a useable single page application is to add a link to the books view on the navmenu.

Open the navmenu.compoment.html file and replace it’s content with the following:

<div class='main-nav'>
<div class='navbar navbar-inverse'>
<div class='navbar-header'>
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
<span class='sr-only'>Toggle navigation</span>
<span class='icon-bar'></span>
<span class='icon-bar'></span>
<span class='icon-bar'></span>
</button>
<a class='navbar-brand' [routerLink]="['/home']">test</a>
</div>
<div class='clearfix'></div>
<div class='navbar-collapse collapse'>
<ul class='nav navbar-nav'>
<li [routerLinkActive]="['link-active']">
<a [routerLink]="['/home']">
<span class='glyphicon glyphicon-home'></span> Home
</a>
</li>
<li [routerLinkActive]="['link-active']">
<a [routerLink]="['/counter']">
<span class='glyphicon glyphicon-education'></span> Counter
</a>
</li>
<li [routerLinkActive]="['link-active']">
<a [routerLink]="['/fetch-data']">
<span class='glyphicon glyphicon-th-list'></span> Fetch data
</a>
</li>
<li [routerLinkActive]="['link-active']">
<a [routerLink]="['/books']">
<span class='glyphicon glyphicon-book'></span> Books
</a>
</li>
</ul>
</div>
</div>
</div>

I’ve highlighted the lines that we’ve added

The routerLinkActive directive is used to apply a class (or a number of classes) to an element when the page URL matches that of the routerLink URL. In our code, we’re applying the ‘link-active’ class.

The routerLink directive takes an array of route names and a collection of query parameters, and these are parsed into full href tags. Here we’re passing ‘/books’ to our button.

Once you added this code, saved and waited for NodeServices to inject the new code into you’re running application, you should see the new button listed on the nav menu:

Angular2 SPA Template Rince Books button added to navmenu
I’ve done a search in this screenshot, to show that everything is working as before.

Conclusion

We’ve gone from an empty directory to a single page application written in TypeScript with Angular 2, in less than an hour. We’ve learnt a little about how Angular2 works

and if you want to know more, take a look at last week’s post

and even connected it to a real service, and used that service to get data.

The future for SPAs is quite exciting, especially if you can build a useful one in under an hour.

Have you built any Angular2 SPAs with the template pack that Microsoft have supplied? If not, are you more willing to do so now that Microsoft have released these template packs?

A .NET developer specialising in ASP.NET MVC websites and services, with a background in WinForms and Games Development. When not programming using .NET, he is either learning about .NET Core (and usually building something cross platform with it), speaking Japanese to anyone who'll listen, learning about languages, writing for his non-dev blog, or writing for a blog about Retro Gaming (which he runs with his brother)