ProudMonkey

ASP.NET Core and Angular 2 Code Venture - Part 1

Introduction

If you stumble upon this article, then I presume you are new to Angular 2 and would like to get your hands dirty with the practical examples. Just like you, I am also pretty much new to Angular 2 and that’s why I am writing this series of articles as a reference to other ASP.NET developers who might want to jump on Angular 2.

Luckily, I had the opportunity to review a book on ASP.NET Web API and Angular 2 by Valerio De Sanctis. So, it’s a good time to incorporate the things that I’ve learned from the book into this series of articles. If you are looking for in-depth information about Angular 2 with ASP.NET Web API, I would highly recommend you to grab yourself a copy of the book here.

In my previous article, we’ve done setting up the backbone of our Angular 2 application. As promised, we will continue exploring Angular 2 within ASP.NET Core by using Web API to work with server-side data.

In this series, we will learn how to build a data-driven Angular 2 app from scratch within the context of ASP.NET Core. Here's a glimpse of the output for this series:

What You Will Learn

In this particular series, you will learn the following:

  • Overview of Angular 2 RC and Core Web API 2
  • Upgrading from Angular 2 Beta to RC6
  • Enable Serving Static Files and Diagnostics
  • Adding the typings.json File
  • Updating the package.json File
  • Updating the tsconfig.json File
  • Updating the AppComponent File
  • Adding the Angular 2 Module File
  • Updating the Boot File
  • Adding the SystemJS File
  • Updating the index.html
  • Switching to Gulp
  • Testing the App
  • Overview of Web Request and Response Flow
  • Creating the Backpacker’s Lounge App
  • Restructuring the App
  • Integrating NewtonSoft.JSON
  • Creating the ViewModels
  • Creating the Web API Controllers
  • Creating the Client-Side ViewModels
  • Creating the Client-Side Service
  • Creating the Angular 2 Components
  • Enabling Client-Side Routing
  • Rewriting
  • Running the App
  • Tips

Before you go any further, though it’s not required, I would suggest you to read my previous article about Getting Started with Angular 2 in ASP.NET Core, since I will not be covering the details about setting up Angular 2 in ASP.NET Core in this series.

Overview

Before we move further, let’s talk about a brief recap about each framework:

Core Web API

ASP.NET Core Web API is a framework built on top of the .NET Core. It’s made specifically for building RESTful services, which can serve a massive range of the clients including Web Browsers, mobile devices and more. ASP.NET Core has a built-in support for MVC building Web APIs. Unifying the two frameworks makes it simpler to build the apps.

Angular 2

Angular 2 is the second major installment of AngularJS and is entirely written in TypeScript. For the developers who are working with Angular 1.x, you might find a big change in Angular 2, as it is entirely component based and an object orientation is much easier with enhanced DI capability.

We can think of Web API as our data transfer gateway that composes a set of Server-side interfaces/endpoints. This set processes request-response messages, typically expressed in a form of JSON or XML. To put it in other words, our Web API will serve as our central gateway to handle the request from the client, perform the Server-side data operations, and then send back the response to the client (the caller). Angular can be described as a modern client-side library that provides rich features which gives the browser the ability to bind input/output parts of a web page to a flexible, reusable and easily testable JavaScript model.

In this part, we will see the Client-Server capabilities of both frameworks and how they interact with each other. In other words, we need to understand how Angular 2 retrieve and transfer the data from ASP.NET Core Web API. We will be using the Angular 2 RC6 and ASP.NET Core 1.0 RTM to build the Application.

Upgrading to Angular2 RC 6

The demo project in my previous article were using Angular 2 Beta version because that’s the current release at that time of writing. Recently, Angular 2 Release Candidate 6 (RC6) version was out. To align with the latest version, I think it’s a good idea to upgrade to keep up with the most stable release and utilize Angular 2 features to their full extent.

You can find the current release change log here: CHANGELOG.md

Important: The current release of Angular 2 requires at least node v4.x.x and npm 3.x.x. Verify that you are running the versions mentioned, by running node -v and npm -v in a terminal/console window. Older versions produce errors.

Let’s Start Modifying!

Before making any file changes, make sure, that you have the supported versions of Node and NPM. If you have the older versions, uninstall node, download and install the required version here: Node.js

NPM is already integrated in the Node installer, so you don’t have to worry about manually upgrading it.

After the installation, make sure to restart your machine to ensure the updates will take effect. In my case, I have the following versions installed:

  • Node v6.3.1 (the current release as of this writing).
  • NPM 3.10.3 (latest version as of this writing).

Keep in mind that this upgrade is also applicable if you were using Angular 2 RC 4 versions and below.

Adding the typings.json File

The typings.json file might not be needed since the release of RC5 and above. If there are cases we need to support backward compatibility and support previous RC versions then, we need to create a new file for creating typings containing node, jasmine and core-js. To do that, just right-click on your project root and then add a new .json file. In my case, I have selected the "NPM Config File" and just renamed it as "typings.json". Replace the default generated content with the following below:

{
  "globalDependencies": {
    "core-js": "registry:dt/core-js#0.0.0+20160602141332",
    "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
    "node": "registry:dt/node#6.0.0+20160621231320"
  }
}

Note: the core-js line in the typings.json file is the only required one, yet we’ve also took the chance to add the jasmine and node typings: you could need them in the near future, should you want to use the Jasmine test framework and/or use code that references objects in the nodejs environment. Keep them there for now, they won’t hurt your project.

Updating the package.json File

If you are not supporting previous versions of angular, I would really recommend you to delete the existing node_modules folder before updating the package.json file. This will ensure that we won’t be having a local copy of the previous angular 2 bundles and modules.

If you have decided, open the package.json file and replace everything in it with the following:

{
  "version": "1.0.0",
  "name": "Your Application Name",
  "author": "Optional Field",
  "description": "Optional Field",
  "dependencies": {
    "@angular/common": "2.0.0-rc.6",
    "@angular/compiler": "2.0.0-rc.6",
    "@angular/core": "2.0.0-rc.6",
    "@angular/http": "2.0.0-rc.6",
    "@angular/platform-browser": "2.0.0-rc.6",
    "@angular/platform-browser-dynamic": "2.0.0-rc.6",
    "@angular/upgrade": "2.0.0-rc.6",
    "@angular/forms": "2.0.0-rc.6",
    "@angular/router": "3.0.0-rc.2",

    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.6",
    "systemjs": "^0.19.37",
    "typings": "^1.3.2",
    "zone.js": "^0.6.12",
    "moment": "^2.14.1"
  },

  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-clean": "^0.3.2",
    "gulp-concat": "^2.6.0",
    "gulp-less": "^3.1.0",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-typescript": "^2.13.6",
    "gulp-uglify": "^2.0.0",
    "typescript": "^1.8.10"
  },
  "scripts": {
    "postinstall": "typings install dt~core-js --global"
  }
}

Note: On 9/14/2016, Angular 2 Final release was out. If you'd like to use the final release version, then the upgrade process from RC6 to Final is pretty easy; We just need to remove the -rc.x suffix from the package.json references.

Here's a quick example using the Final release version:

"@angular/common": "2.0.0",
"@angular/compiler": "2.0.0",
"@angular/core": "2.0.0",
"@angular/forms": "2.0.0",
"@angular/http": "2.0.0",
"@angular/platform-browser": "2.0.0",
"@angular/platform-browser-dynamic": "2.0.0",
"@angular/router": "3.0.0",
"@angular/upgrade": "2.0.0",

The packages with the @ symbol is part of the new Angular 2 bundle: the other ones are loading libraries, helper tools and polyfills for older browser support. The current version of Angular 2 as of this writing is 2.0.0-rc.6. You can check for the updates here. You may also have noticed that we’ve replaced the dependencies to Grunt with Gulp ~ I’ll explain why later in this article. You may remove the existing gruntfile.js file just like what I did if you’d like, but if you prefer using Grunt, feel free to do so. No one is going to fire you for using Grunt :)

You may also have noticed that we’ve added the scripts section to install the typings. Typings manages and install the TypeScript definitions.

Now, save the file to restore the needed packages for our application.

Once installed, you should be able to see something like this:

Figure 1: Application Dependencies

Tip: If Typings didn’t install successfully on Save, the try to use the "Restore Package" option by right-clicking on the "Dependencies" node. Another way is to use the command line to run typings explicitly. To do that, just navigate to the root folder of your app and do a CTRL+SHIFT, then select "Open command window here". In the command line, type in the following command:

npm run typings install

If successful, you should be able to see something like this:
Figure 2: Command Line

As a recap, all Angular 2 dependencies will be installed in the following location in your local drive:

../src/<YourProjectName>/node_modules

Updating the tsconfig.json File

Now open your tsconfig.json file, if you do not have it, you need to create one. Here’s the updated TypeScript JSON Configuration file:

{
  "compileOnSave": false,
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "system",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noEmitOnError": false,
    "removeComments": false,
    "target": "es5"
  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

The compileOnSave signals the IDE to generate all files for a given tsconfig.json upon saving. compilerOptions configuration will influence how the Intellisense, and our external TypeScript compiler will work. By excluding the folders in the config, we tell the built-in TypeScript compiler provided by Visual Studio 2015 to disable compiling the external TypeScript files within that location.

Updating the AppComponent File

Since we are upgrading to RC version, we also need to update the component that have references to previous beta version of Angular. In our particular example, we need to update our app.component.ts file to this:

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

@Component({
    selector: 'angularjs2demo',
    template: `<h1>AngularJS 2 Demo</h1><div>Hello ASP.NET Core! Greetings from AngularJS 2.</div>`
})

export class AppComponent { }  

There’s not much to say about the update above. We just changed the import reference to the new name which is @angular/core.

Adding the Angular 2 Module File

Angular Modules, also known as NgModules, have been introduced since Angular2 RC5. This provides a powerful way to organize and bootstrap any Angular2 application: they help developers to consolidate their own set of components, directives and pipes into reusable blocks.

Every Angular2 application since RC5 must have at least one module, which is conventionally called root module and given the AppModule class name.

Now, create a new TypeScript file and name the file as "app.module.ts". If you are following my previous article here, then you may create the file under Scripts/app folder. Here’s how the app.module.ts file should look like:

///<reference path="../../typings/index.d.ts"/>
import {NgModule} from "@angular/core";  
import {BrowserModule} from "@angular/platform-browser";  
import {HttpModule} from "@angular/http";  
import "rxjs/Rx";

import {AppComponent} from "./app.component";

@NgModule({
    // directives, components, and pipes
    declarations: [
        AppComponent
    ],
    // modules
    imports: [
        BrowserModule,
        HttpModule
    ],
    // providers
    providers: [
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { }  

The first line from the config above, adds a reference to the type definitions to ensure our TypeScript compiler will find it. Note that, in case we’re using a CDN or a pre-compiled version of Angular2, we could (and should) definitely remove this line. We then import the basic Angular2 modules that we will need for our app. You can add more Angular 2 module reference in this file when needed. We have also imported the rxjs library definition file(s), which will be useful to compile some Angular2 libraries. We then imported our component "AppComponent". Finally, we have declared our root NgModule: as we can see it consists in an array of named arrays, each one containing a set of Angular2 objects that serves a common purpose: directives, components, pipes, modules and providers. The last one of them contains the component(s) we want to bootstrap, which in our case, the AppComponent file.

For more information about App Module, see: https://angular.io/docs/ts/latest/cookbook/rc4-to-rc5.html

Updating the Boot File

Open boot.ts file and replace the existing code with the following:

import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";  
import {AppModule} from "./app.module";

platformBrowserDynamic().bootstrapModule(AppModule);  

Just like in the app.component.ts file, we changed the reference to the new Angular bundle. Notice that we have referenced the new AppModule that we have created previously. Now we’re just missing an entry point to load with the browser: let’s add it right now.

Adding the SystemJS File

Now, let’s add the systemjs configuration file. Right-click on the wwwroot folder and then select Add > New Item. Under Client-side templates, select "JavaScript File" just like in the figure shown below:

Figure 3: Add New Item Dialog

Name the file as "systemjs.config.js" and then copy the following code below:

(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': 'js/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',

            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

            // angular testing umd bundles
            '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
            '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
            '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
            '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
            '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
            '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
            '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
            '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',

            // other libraries
            'rxjs': 'npm:rxjs',
            'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './boot.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            },
            'angular2-in-memory-web-api': {
                defaultExtension: 'js'
            }
        }
    });
})(this);

The SystemJS.config file will load the application and library modules. We are going to need this loader to build our Angular2 app. For more details about SystemJS config, read on: SystemJS

If you want to use WebPack as your module bundler, see: Introduction to WebPack

Updating the index.html

Now, since we need the SystemJS config to load our app modules and components, then we need to update our index.html to include the new configuration. Replace your index.html file so it will look something like this:

<html>  
<head>  
    <title>ASP.NET Core with Angular 2 RC Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Step 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="js/shim.min.js"></script>
    <script src="js/zone.js"></script>
    <script src="js/Reflect.js"></script>
    <script src="js/system.src.js"></script>

    <!-- Angular2 Native Directives -->
    <script src="/js/moment.js"></script>

    <!-- Step 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>  
<!-- Step 3. Display the application -->  
<body>  
    <!-- Application PlaceHolder -->
    <angularjs2demo>Please wait...</angularjs2demo>
</body>  
</html>  

Switching to Gulp

This time, we will be using Gulp as the JavaScript Task Runner to automate our client-side scripts. I’d like to note that switching to Gulp does not mean Grunt is a bad tool. Of course Grunt is still a great tool and used by many. If you prefer, you can always use it to automate tasks for you. I just preferred to use Gulp because of its conciseness and easy task writing. It also uses node.js’ streams, and executes faster, since it does not open/close files, or creates intermediary copies all the time. But then again, every developer has preference, so it’s all up to you which JS task runners you would use :).

Now, let’s add a new file for our Gulp configuration. Go ahead and right click on the project solution and then select Add > New Item. Under Client-side template, select "Gulp Configuration File" as shown in the figure below:
Figure 4: Add New File

Click Add to generate the file, and then replace the default generated configuration with the following code below:

var gulp = require('gulp'),  
    gp_clean = require('gulp-clean'),
    gp_concat = require('gulp-concat'),
    gp_less = require('gulp-less'),
    gp_sourcemaps = require('gulp-sourcemaps'),
    gp_typescript = require('gulp-typescript'),
    gp_uglify = require('gulp-uglify');

/// Define paths
var srcPaths = {  
    app: ['Scripts/app/main.ts', 'Scripts/app/**/*.ts'],
    js: [
        'Scripts/js/**/*.js',
        'node_modules/core-js/client/shim.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system.src.js',
        'node_modules/typescript/lib/typescript.js',
        'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js',
        'node_modules/moment/moment.js'
    ],
    js_angular: [
        'node_modules/@angular/**'
    ],
    js_rxjs: [
        'node_modules/rxjs/**'
    ]
};

var destPaths = {  
    app: 'wwwroot/app/',
    js: 'wwwroot/js/',
    js_angular: 'wwwroot/js/@angular/',
    js_rxjs: 'wwwroot/js/rxjs/'
};

// Compile, minify and create sourcemaps all TypeScript files 
// and place them to wwwroot/app, together with their js.map files.
gulp.task('app', ['app_clean'], function () {  
    return gulp.src(srcPaths.app)
        .pipe(gp_sourcemaps.init())
        .pipe(gp_typescript(require('./tsconfig.json').compilerOptions))
        .pipe(gp_uglify({ mangle: false }))
        .pipe(gp_sourcemaps.write('/'))
        .pipe(gulp.dest(destPaths.app));
});

// Delete wwwroot/app contents
gulp.task('app_clean', function () {  
    return gulp.src(destPaths.app + "*", { read: false })
    .pipe(gp_clean({ force: true }));
});

// Copy all JS files from external libraries to wwwroot/js
gulp.task('js', function () {  
    gulp.src(srcPaths.js_angular)
        .pipe(gulp.dest(destPaths.js_angular));
    gulp.src(srcPaths.js_rxjs)
        .pipe(gulp.dest(destPaths.js_rxjs));
    return gulp.src(srcPaths.js)
        .pipe(gulp.dest(destPaths.js));
});


// Watch specified files and define what to do upon file changes
gulp.task('watch', function () {  
    gulp.watch([srcPaths.app, srcPaths.js], ['app', 'js']);
});

// Define the default task so it will launch all other tasks
gulp.task('default', ['app', 'js', 'watch']);  

The code above contains three (3) main variables:

  • gulp - initializes each Gulp plugins that we are going to need for running the tasks.
  • srcPaths - holds an array of sources that we want to copy and transpile.
  • destPaths - holds an array of specific location within wwwroot. This is basically where we dump the scripts we defined from the srcPaths.

It also contains five (5) main tasks:

  • app_clean - This task deletes the existing files from the destination folders we defined.
  • app - This task compiles, uglify and create sourcemaps for all TypeScript files and place them to wwwroot/app folder, together with their js.map files.
  • js - This task will copy all JavaScript files from external libraries which is located within the node_modules folder and place them to wwwroot/js folder.
  • watch - This task watches files defined in app and js tasks that are changed.
  • default - Define the default task so it will launch all other tasks

Testing the App

Clean and Build your solution and make sure it has no errors. If it builds successfully, then right click on the gulpfile.js and select "Task Runner Explorer"

Make sure to click the "Refresh" button to load the task as shown in the figure below:

Figure 5: Task Runner

Now, right-click to the default task and hit Run. You should be able to see something like this when successful.
Figure 6: Task Running

After successfully running the Task Runner, you should also see that the "app" and "js" folders are generated within your "wwwroot" folder as shown in the figure below:

Figure 7: File Transpiled

Running the application should result to something like this:

Figure 8: Output

If you’ve made it this far, congrats! You have now an Angular 2 RC4 running and now ready to work with the data, using Core Web API.

Tips:

If you are getting the following Typescript Build errors:

“TS2664 Invalid module name in augmentation, module '../../Observable' cannot be found.”

Just follow the solution provided here: https://github.com/Microsoft/TypeScript/issues/8518#issuecomment-229506507

In my case, since I’m using VStudio 2015 Update 3, the fix was to replace the typescriptService.js with the updated version mentioned from the link above. In other words, replace the content of:

C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TypeScript\typescriptServices.js

with this:

https://raw.githubusercontent.com/Microsoft/TypeScript/Fix8518/lib/typescriptServices.js

Make sure to backup your existing typescriptService.js file before updating.

If you are getting the errors during compilation that says something like these:

TS1005 Build:'=' expected. TS1005 Build:';' expected.

Just make sure, you exclude the wwwroot in your ts.config. If it doesn’t work, you can try the following options for the fix:

  • Option 1: Filtering out the location, which contains the *.d.ts files from compiling in your ts.config file.
  • Option 2: Upgrade to TypeScript 1.9.x or greater Beta versions by running the following command within your project root:

npm install typescript@next

Creating the Application

Before we get our hands dirty, let’s have a quick overview on what happens, when a request is issued by the client and how the Server sends out a JSON Response by looking at the figure, given below:

Figure 9: Web Request and Response Flow

Async Data Requests are any user interactions, as long as it needs to retrieve data from the server, a very typical example includes (yet is not limited to): clicking a button to show data or to edit/delete something, navigating to another view, submitting a form and so on. And in order to respond to any client-issued Async Data Request, we need to build a server-side Web API Controller that would handle the client request (CRUD). In other words, the API will process the request, perform database CRUD operations and return a serialized ViewModel to the client as a Response.

To understand how Angular and Web API connects to each other in real scenarios then let’s go ahead and start building our app.

The Backpacker’s Lounge App

For the entire series, we will be creating an application that covers a bunch of CRUD operations. I love to travel and explore the wonders of nature, so to make this app different and interesting for starters, I have decided to build a simple Backpackers Lounge App.

To make it more clear, we will be creating something like this:
Figure 10: The Main App Layout

In this part of the series, we are going to focus on creating the Home content. Meaning, we are going to create the components for displaying the latest discussions, latest places added and the most viewed places. We are also going to setup the client-side routing and see how the navigation works in Angular2. Adding to that, we will implement a simple master-detail navigation to see how we are going to pass information between components. Finally, we are going to see the basics on how the two-way data-binding will work in Angular2.

Looking at the figure above, we are going to need the following sets of API calls:

  • api/Lounge/GetLatestDiscussion
  • api/Place/GetLatestEntries
  • api/Place/GetMostViewed

The GetLatestDiscussion will return a list of items for discussion. This API method will be hosted within the LoungeController class.

The GetLatestEntries will return a list of items for new places added. The GetMostViewed will return a list of items for places that has the top page views. Both of these API methods will be hosted within the PlaceController class as the API URI defined above suggests.

Restructuring the App

Before we move further, let’s revamp our application structure so it would be more maintainable and to value the separation of concerns.

Looking at the picture shown in figure 10, we need to add some folders to our existing "app" folder and then move some existing files.

Let’s start by adding the following folders below under Scripts/app folder:

  • components
  • services
  • viewmodels

The components folder is where we store all TypeScript files for Angular 2 related components.

The services folder is where we store our TypeScript based services for communicating with ASP.NET Core Web API.

The viewmodels folder is where we store our TypeScript based strongly-typed view models.

Now, under the components folder, add the following sub-folders:

  • home
  • lounge
  • about
  • account
  • explore

If you have noticed, the folders above matched with our navigation menu shown in figure 10. Note that the folders don’t really need to be the same name as the menus. You can name the folders to whatever you like, but for this demo, let’s stick to that for easy reference. Each folder is where we store the corresponding components for specific features in the website.

Once you have all of that folders set, move the "app.component.ts" to the components folder. Your project structure should now look something like this:

Figure 11: The Components Folders

Since we moved the location of our app.component.ts file, then we also need to update the import reference of that file in our app.module.ts file:

import {AppComponent} from "./components/app.component";  

Integrating NewtonSoft.JSON

If you’re working with ASP.NET and you’ve never heard about Newtonsoft’s Json.NET, you most certainly missed something that could’ve eased your job. Big time. We’re talking of one of the finest libraries – and most useful tools – ever developed for .NET, at least for the writer: a very efficient (and thus very popular), high-performance JSON serializer, deserializer and all-around framework for .NET, which also happens to be completely Open Source.

To add the Newtonsoft.JSON in our project, go ahead and right click on the project root and then select Manage NuGet Packages. Under "Browse" tab, enter "newtonsoft" in the search bar and it should display the package just like in the following:
Figure 12: Manage Nuget Packages

The latest version of Newtonsoft.Json is 9.01 as of the time of writing. Click Install to add the dependency in our project. After successful installation, you should be able to see the Newstonsoft.Json added in our project references.

Creating the ViewModels

To provide you a quick recap, ViewModels are just classes that house some properties that we only need in the View/UI. From this moment, we are going to use ViewModels as our data transfer objects: sending data from client to server and/or vice-versa.

In this part, we will not be using any database at this point for us to test something out. We’ll just put together some static test data in order to understand how to pass them back and forth by using a well-structured and highly-configurable interface. One of the great things in building a native Web App using ASP.NET and Angular2 is that we can start writing our code without worrying much about the data source: they will come later, and only after we’re sure about what we really need.

Now, it’s time to create the server-side ViewModels.

Create a new folder at the root of your project and name it as "Data" and under it, create a sub-folder and name it as "ViewModels". Within that folder, create the following class:

  • LoungeViewModel.cs
  • PlaceViewModel.cs

We should now have the following structure:
Figure 13: ViewModels Folder

Here are the codes for each class:

The LoungeViewModel.cs

Update the default code generated so it would look similar to the following code below:

using System;  
using Newtonsoft.Json;

namespace TheBackPackerLounge.Data.ViewModels  
{
    [JsonObject(MemberSerialization.OptOut)]
    public class LoungeViewModel
    {
        public int ID { get; set; }
        public string Subject { get; set; }
        public string Message { get; set; }
        [JsonIgnore]
        public int ViewCount { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime LastModifiedDate { get; set; }
    }
}

The PlaceViewModel.cs

And here’s the code for the PlaceViewModel class:

using System;  
using Newtonsoft.Json;

namespace TheBackPackerLounge.Data.ViewModels  
{
    [JsonObject(MemberSerialization.OptOut)]
    public class PlaceViewModel
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Location { get; set; }
        [JsonIgnore]
        public int ViewCount { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime LastModifiedDate { get; set; }
    }
}

Both ViewModels above are nothing but just classes that house some properties. Each class is decorated with the [JsonObject(MemberSerialization.OptOut)] attribute that causes the properties to be serialized into JSON unless being decorated by an explicit [JsonIgnore]. We’re making this choice because we’re going to need most of our ViewModel’s properties serialized, as we’ll be seeing soon enough.

We are also limiting the properties defined in our ViewModels at this point. Eventually, we will be adding more to them once we integrate Authorization, Authentication and Image for places. So for now let’s keep rolling.

Creating the Web API Controllers

Now, let’s create the needed API Controllers in our app.

The LoungeController

Create a new class under "Controllers" folder and name it as "LoungeController". To do that, just right click on the "Controllers" folder and then select Add > New Item. Under ASP.NET template, select "Web API Controller Class". Replace the default generated code with the following:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Threading.Tasks;  
using Microsoft.AspNetCore.Mvc;  
using TheBackPackerLounge.Data.ViewModels;  
using Newtonsoft.Json;


namespace TheBackPackerLounge.Controllers  
{
    [Route("api/[controller]")]
    public class LoungeController : Controller
    {
        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            return new JsonResult(GetTestData().Where(i => i.ID == id), DefaultJsonSettings);
        }

        [HttpGet("GetLatestDiscussion")]
        public IActionResult GetLatestDiscussion()
        {
            return GetLatestDiscussion(DefaultNumberOfItems);
        }

        [HttpGet("GetLatestDiscussion/{n}")]
        public IActionResult GetLatestDiscussion(int n)
        {
            var data = GetTestData().OrderByDescending(i => i.CreatedDate).Take(n);
            return new JsonResult(data, DefaultJsonSettings);
        }

        private List<LoungeViewModel> GetTestData(int num = 99)
        {
            List<LoungeViewModel> list = new List<LoungeViewModel>();
            DateTime date = DateTime.Now.AddDays(-num);
            for (int id = 1; id <= num; id++)
            {
                list.Add(new LoungeViewModel()
                {
                    ID = id,
                    Subject = String.Format("Discussion {0} Subject", id),
                    Message = String.Format("This is a sample message for Discussion {0} Subject: It's more fun in the Philippines", id),
                    CreatedDate = date.AddDays(id),
                    LastModifiedDate = date.AddDays(id),
                    ViewCount = num - id
                });
            }
            return list;
        }

        private JsonSerializerSettings DefaultJsonSettings
        {
            get
            {
                return new JsonSerializerSettings()
                {
                    Formatting = Formatting.Indented
                };
            }
        }

        private int DefaultNumberOfItems
        {
            get
            {
                return 10;
            }
        }

        private int MaxNumberOfItems
        {
            get
            {
                return 50;
            }
        }
    }

}

The class above make use of Attribute Routing to determine that the class is an API by decorating with this attribute: [Route("api/[controller]")].

The class above contains the following three (3) main API action methods:

  • Get() – This method returns a single set of data based on a given ID. This method can be invoked using: /api/lounge/ where id is a variable
  • GetLatestDiscussion() – This method represents a RESTFUL API method, and just calls it’s overload method to actually process the request. This method can be invoked using: /api/lounge/getlatestdiscussion
  • GetLatestDiscussion(int n) – This method is the overload of GetLatestDiscussion() which takes an int parameter. It use the LINQ OrderByDescending() and Take operators to get the latest records from our test data. This method can be invoked using: /api/lounge/getlatestdiscussion/<n> where n is a variable, representing a number.

It also contains the following private methods:

  • GetTestData(int num = 99) - A test method that returns a static list of items. We will be using this method to generate a list of LoungViewModel just for us to test something out quickly.
  • DefaultJsonSettings - A property that gets the default JSON format settings
  • DefaultNumberOfItems - A property that returns the defaul number of items to be returned in the UI.
  • MaxNumberOfItems - A property that returns the default maximum number of items to be returned to the UI.

We are going to use the aforementioned private members for displaying a test data in our app. We will for sure split and decouple our data access from within our Controller in next part of this series, but for now, let’s just keep rolling.

The PlaceController

Create another Web API Controller Class and name it as "PlaceController". Again, replace the default code with the following:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Threading.Tasks;  
using Microsoft.AspNetCore.Mvc;  
using TheBackPackerLounge.Data.ViewModels;  
using Newtonsoft.Json;

namespace TheBackPackerLounge.Controllers  
{
    [Route("api/[controller]")]
    public class PlaceController : Controller
    {

        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            return new JsonResult(GetTestData().Where(i => i.ID == id), DefaultJsonSettings);
        }

        [HttpGet("GetLatestEntries")]
        public IActionResult GetLatestEntries()
        {
            return GetLatestEntries(DefaultNumberOfItems);
        }

        [HttpGet("GetLatestEntries/{n}")]
        public IActionResult GetLatestEntries(int n)
        {
            var data = GetTestData().OrderByDescending(i => i.CreatedDate).Take(n);
            return new JsonResult(data, DefaultJsonSettings);
        }

        [HttpGet("GetMostViewed")]
        public IActionResult GetMostViewed()
        {
            return GetMostViewed(DefaultNumberOfItems);
        }

        [HttpGet("GetMostViewed/{n}")]
        public IActionResult GetMostViewed(int n)
        {
            if (n > MaxNumberOfItems) n = MaxNumberOfItems;
            var data = GetTestData().OrderByDescending(i => i.ViewCount).Take(n);
            return new JsonResult(data, DefaultJsonSettings);
        }

        private List<PlaceViewModel> GetTestData(int num = 999)
        {
            List<PlaceViewModel> list = new List<PlaceViewModel>();
            DateTime date = DateTime.Now.AddDays(-num);
            for (int id = 1; id <= num; id++)
            {
                list.Add(new PlaceViewModel()
                {
                    ID = id,
                    Name = String.Format("Place {0} Name", id),
                    Location = String.Format("Place {0} Location", id),
                    CreatedDate = date.AddDays(id),
                    LastModifiedDate = date.AddDays(id),
                    ViewCount = num - id
                });
            }
            return list;
        }

        private JsonSerializerSettings DefaultJsonSettings
        {
            get
            {
                return new JsonSerializerSettings()
                {
                    Formatting = Formatting.Indented
                };
            }
        }

        private int DefaultNumberOfItems
        {
            get
            {
                return 5;
            }
        }

        private int MaxNumberOfItems
        {
            get
            {
                return 100;
            }
        }
    }
}

You may have noticed that both classes have the pretty much similar implementation. We could probably merge it in one Controller, but I have decided to split it with different class to value the separation of concerns.

You may also have noticed that both classes have the same private members. We could minimize that by creating a separate class/base class, and implement the code there. But for now, let’s just have it on separate class for easy reference.

Creating the Client-Side ViewModels

We will be using TypeScript to define a set of class for us to work with type definitions. In other words, we will not be dealing with raw JSON data and anonymous objects; instead we will be using typed objects: an actual instance of classes.

Under Scripts/app/viewmodels folder, add a new TypeScript file and name it as "lounge.ts". Then copy the following code below:

export class Lounge {  
    constructor(
        public ID: number,
        public Subject: string,
        public Message: string
    ) { }
}

Add another TypeScript file and name it as "place.ts". Copy the following code below:

export class Place {  
    constructor(
        public ID: number,
        public Name: string,
        public Location: string
    ) { }
}

Notice that we’re not adding all the properties that are present in our server-side ViewModel class: as a general rule of the thumb, we’ll be keeping these classes as lightweight as possible, defining only what we need in the UI: we can always add more properties later, as soon as we need them.

These ViewModels will be used as a client-side, TypeScript class to properly map our JSON-serialized server-side ViewModels that is returned from the Web API controller.

Note: The property names should match with the property names you defined in your server-side ViewModels including the casing.

Creating the Client-Side Service

Now, we need to setup a client-service to fetch the required data from the Web API: we’ll do that by issuing a request to the API Controllers we built earlier. We are going to use the Angular Http client to communicate via XMLHttpRequest (XHR), which is a rather complex HTTP-based API that provides client functionality for transferring data between a client and a server.

The AppService Class

Create another TypeScript file under "Scripts/app/services" folder and name it as "app.service.ts". Copy the following code below:

import {Injectable} from "@angular/core";  
import {Http, Response} from "@angular/http";  
import {Lounge} from "../viewmodels/lounge";  
import {Observable} from "rxjs/Observable";

@Injectable()
export class AppService {  
    constructor(private http: Http) { }

     // URL to web api
    private loungeBaseUrl = 'api/lounge/';
    private placeBaseUrl = 'api/place/'; 

    getLatestDiscussion(num?: number) {
        var url = this.loungeBaseUrl + "GetLatestDiscussion/";
        if (num != null) url += num;
        return this.http.get(url)
            .map(response => response.json())
            .catch(this.handleError);
    }

    getDiscussion(id: number) {
        if (id == null) throw new Error("id is required.");
        var url = this.loungeBaseUrl + id;
        return this.http.get(url)
            .map(response => <Lounge>response.json())
            .catch(this.handleError);
    }

    getLatestEntries(num?: number) {
        var url = this.placeBaseUrl + "GetLatestEntries/";
        if (num != null) url += num;
        return this.http.get(url)
            .map(response => response.json())
            .catch(this.handleError);
    }

    getMostViewed(num?: number) {
        var url = this.placeBaseUrl + "GetMostViewed/";
        if (num != null) url += num;
        return this.http.get(url)
            .map(response => response.json())
            .catch(this.handleError);
    }

    getPlace(id: number) {
        if (id == null) throw new Error("id is required.");
        var url = this.placeBaseUrl + id;
        return this.http.get(url)
            .map(response => <Lounge>response.json())
            .catch(this.handleError);
    }

    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || "Server error");
    }
}

Notice that the code above pretty much resembles our Web API Controllers methods, except that we have merged all the calls to our API within it. This will be the class that our client will use to fetch the data from our WebAPI Controller itself.

Keep in mind that we make use of the Injectable decorator, declaring that the service is an Injectable class: doing that will attach to our class a set of meta data that will be consumed by the DI System upon instantiation. Basically what we’re doing here is telling the DI injector that the constructor parameter(s) should be instantiated by using their declared type(s). The TypeScript code allows a very fluent syntax to achieve this result at constructor level, as it can be seen in the following line:

    constructor(private http: Http) { }

Creating the Angular 2 Components

The next thing that we are going to do is to create a dedicated component for serving different contents. Let’s start by implementing the "Latest Discussion" master-detail components.

The LoungeList Component

Create a new TypeScript file under "Scripts/app/components/lounge" and name the file as "lounge-list.component.ts". Copy the following code below within the file:

import {Component, OnInit} from "@angular/core";  
import {Router} from "@angular/router";  
import {Lounge} from "../../viewmodels/lounge";  
import {AppService} from "../../services/app.service";

@Component({
    selector: "lounge-list",
    template: `
            <h2>{{title}}</h2>
            <ul class="items">
                <li *ngFor="let item of items" 
                    [class.selected]="item === selectedItem"
                    (click)="onSelect(item)">
                    <span>{{item.Subject}}</span>
                </li>
            </ul>
    `,
    styles: [`
        ul.items li { 
            cursor: pointer;
        }
        ul.items li:hover { 
            background-color: #E8FAEC; 
        }
    `]
})

export class LoungeListComponent implements OnInit {  
    title: string;
    selectedItem: Lounge;
    items: Lounge[];
    errorMessage: string;

    constructor(private AppService: AppService, private router: Router) { }

    ngOnInit() {
        this.title = "The Lounge";
        var service = this.AppService.getLatestDiscussion();

        service.subscribe(
            items => this.items = items,
            error => this.errorMessage = <any>error
        );

    }

    onSelect(item: Lounge) {
        this.selectedItem = item;
        var link = ['/lounge', this.selectedItem.ID];
        this.router.navigate(link);
    }
}

The LoungeListComponent will display the latest list of discussions from our mocked data, which is returned from the Web API call. Let’s talk a bit about what we did there:

At the top of the file, we have imported the Angular classes that we need: since we’re creating a Component, we need the Component base class by referencing the @angular/core, and we also need to implement the OnInit interface because our component needs to execute something upon its initialization. We have referenced the Angular2 Router to make use of client-side navigation, because we are going to need it for navigating to the details component. We have also referenced the service that we have created earlier to communicate with our server to get some data. Finally, we have imported the lounge viewmodel for storing the values.

The @component block is where we setup the UI for our Component, including the selector, template and styles. Notice that we used a bit of Angular2 Template Syntax there, in order to get the job done. Specifically, we used a master template, a ngFor directive, a property binding and an event binding. Note that we can also decouple the template and styles in a separate file by defining the templateUrl and styleUrls.

The LoungeListComponent is a class written in TypeScript. This class contains some properties, a constructor which makes use of DI to instantiate the AppService and Router objects. It also composed of methods that will be used in the component itself, specifically in the Angular2 template manipulation, and of course in the client-side routing. The ngOnInit() method is where we get the data from the service which fires on initialization of the component. The onSelect() method takes a Lounge typed object as the parameter. This is where we get the selected item and pass it to the details component by making use of Angular2 routing. For information about Angular 2 Templates, read on: Template Syntax

The LoungeDetail Component

Create another TypeScript component within "Scripts/app/components/lounge" and name the file as "lounge-detail.component.ts". Now copy the following code below within that file:

import {Component, OnInit, OnDestroy} from "@angular/core";  
import {Router, ActivatedRoute} from "@angular/router";  
import {AppService} from "../../services/app.service";  
import {Lounge} from "../../viewmodels/lounge";

@Component({
    selector: "lounge-detail",
    template: `
        <div *ngIf="item" class="item-details">
          <h2>{{item.Subject}} - Detail View</h2>
          <ul>
              <li>
                  <label>Subject:</label>
                  <input [(ngModel)]="item.Subject" placeholder="Insert the title..."/>
              </li>
              <li>
                  <label>Message:</label>
                  <textarea [(ngModel)]="item.Message" placeholder="Insert a message..."></textarea>
              </li>
          </ul>
        </div>

        <div>
               <button (click)='onBack()'>Back to Home</button>
        </div>
    `,
    styles: [`
        .item-details {
            margin: 5px;
            padding: 5px 10px;
            border: 1px solid 9BCCE0;
            background-color: #DDF0D5;
            width: 500px;
        }
        .item-details * {
            vertical-align: middle;
        }
        .item-details ul li {
            padding: 5px 0;
        }
    `]
})

export class LoungeDetailComponent implements OnInit {  
    item: Lounge;
    sub: any;

    constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { }

    ngOnInit() {
        this.sub = this.route.params.subscribe(params => {
            var id = +params['id'];
            console.log("selected id " + id);
            this.AppService.getDiscussion(id).subscribe(item => this.item = item[0]);
        });
    }

    ngOnDestroy() {
        this.sub.unsubscribe();
    }

    onBack(): void {
        this.router.navigate(['/home']);
    }
}

Just like what we did in the lounge-list.component.ts file, we are importing what we need for this specific component. Within template, noticed that we've used the Angular2 ngIf directive to hide the DOM elements when the property item is null or no data associated on it. We also used the Angular2 ngModel directive to implement a two-way data binding syntax for textarea element. This could simply mean that any changes made in the binded element will automatically update the model itself and vice-versa.

We also applied some simple styles to our HTML by defining the @component's styles attribute just to beautify a bit our rendered markup.

The LoungeDetailComponent class implements the logic for this particular component. We have defined a few properties and make use of constructor injection (DI) to initialize objects what we need for the class implementation. This class takes the id parameter from the params observable in the ActivatedRoute service and use the AppService to fetch the data based on the id. You should be able to see how we did that under the ngOnInit() method. The ngOnDestroy() method is responsible for cleaning up the params subscription. Finally, we have implemented an onBack() method to let user navigate back to the Home component view. This event is attached in the Button element defined in our HTML template using Angular2 event binding.

Keep in mind that we will be defining the directives and providers that the Component needs within our app.module.ts file which we will be seeing soon enough.

At this point, we’re done with our basic Lounge master-detail navigation and data binding. Now, let’s do the same to implement the "What’s New?" and "Top Places to Visit" master-detail components.

The PlaceList Component

Create a new TypeScript file under "Scripts/app/components/explore" folder and name the file as "place-list.component.ts". Copy the following code below within the file:

import {Component, Input, OnInit} from "@angular/core";  
import {Router} from "@angular/router";  
import {Place} from "../../viewmodels/place";  
import {AppService} from "../../services/app.service";

@Component({
    selector: "place-list",
    template: `
            <h2>{{title}}</h2>
            <ul class="items">
                <li *ngFor="let item of items" 
                    (click)="onSelect(item)">
                    <span>{{item.Name}}</span>
                </li>
            </ul>
    `,
    styles: [`
        ul.items li { 
            cursor: pointer;
        }
        ul.items li:hover { 
            background-color: #E8FAEC; 
        }
    `]
})

export class PlaceListComponent implements OnInit {  
    @Input() class: string;
    title: string;
    items: Place[];
    errorMessage: string;

    constructor(private AppService: AppService, private router: Router) { }

    ngOnInit() {

        var service = null;
        switch (this.class) {
            case "latest":
            default:
                this.title = "What's New?";
                service = this.AppService.getLatestEntries();
                break;
            case "most-viewed":
                this.title = "Top Places to Visit";
                service = this.AppService.getMostViewed();
                break;
        }

        service.subscribe(
            items => this.items = items,
            error => this.errorMessage = <any>error
        );
    }

    onSelect(item: Place) {
        var link = ['/explore', item.ID];
        this.router.navigate(link);
    }
}

The code above has similar implementation with our LoungeListComponent except that we did something different in the ngOnInit() method. We have implemented a switch statement to determine which data to load in the component by passing the class variable as the parameter. The class variable is defined using an @Input() decorator function (@Input() class: string;), that will add the required metadata to make this property available for property binding. We need to do that because we expect this property to be populated from a binding expression within a parent component ~ the Home Component. We did it like that to make this component more reusable and maintainable since the data for "What’s New?" and "Top Places to Visit" components are coming from the same data source.

The PlaceDetail Component

The details for "What’s New?" and "Top Places to Visit" components will also share the same Detail-View component. Let’s go ahead and create it. Add a new TypeScript file on the same folder and name it as "place-detail.component.ts". Now copy the following code below:

import {Component, OnInit, OnDestroy} from "@angular/core";  
import {Router, ActivatedRoute} from "@angular/router";  
import {AppService} from "../../services/app.service";  
import {Place} from "../../viewmodels/place";


@Component({
    selector: "place-detail",
    template: `
        <div *ngIf="item" class="item-details">
          <h2>{{item.Name}} - Detail View</h2>
          <ul>
              <li>
                  <label>Subject:</label>
                  <input [(ngModel)]="item.Name" placeholder="Insert the name..."/>
              </li>
              <li>
                  <label>Message:</label>
                  <textarea [(ngModel)]="item.Location" placeholder="Insert a location..."></textarea>
              </li>
          </ul>
        </div>

        <div>
               <button (click)='onBack()'>Back to Home</button>
        </div>
    `,
    styles: [`
        .item-details {
            margin: 5px;
            padding: 5px 10px;
            border: 1px solid 9BCCE0;
            background-color: #DDF0D5;
            width: 500px;
        }
        .item-details * {
            vertical-align: middle;
        }
        .item-details ul li {
            padding: 5px 0;
        }
    `]
})

export class PlaceDetailComponent implements OnInit {  
    item: Place;
    sub: any;

    constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { }

    ngOnInit() {
        this.sub = this.route.params.subscribe(params => {
            var id = +params['id'];
            this.AppService.getPlace(id).subscribe(item => this.item = item[0]);
        });
    }

    ngOnDestroy() {
        this.sub.unsubscribe();
    }

    onBack(): void {
        this.router.navigate(['/home']);
    }
}

The code above is pretty much the same as what we did for implementing the LoungeDetailComponent, so there’s really not much to talk about.

The Home Component

Create a new TypeScript file under "Scripts/app/components/home" folder and name the file as "home.component.ts". Copy the following code below within that file:

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

@Component({
    selector: "home",
    template: `
        <div class="div-wrapper">
            <div class="div-lounge">
                   <div>
                        <lounge-list class="discussion"></lounge-list>
                   </div>
            </div>
            <div class="div-explore">
                  <div class="top">
                        <place-list class="latest"></place-list>
                  </div>
                  <div class="bot">
                        <place-list class="most-viewed"></place-list>
                  </div>
            </div>
            <div class="div-clear"></div>
        </div>
    `,
    styles: [`
        .div-wrapper {
          margin-right: 300px;
        }
        .div-lounge {
          float: left;
          width: 100%;

        }
        .div-lounge div{
           margin:0 0 10px 0;
           border: 1px solid #9BCCE0;
           background-color: #DDF0D5;
        }
        .div-explore {
          float: right;
          width: 300px;
          margin-right: -300px;
        }
       .div-explore .top{
           margin:0 10px 10px 10px;
           border: 1px solid #9BCCE0;
           background-color: #DDF0D5;
        }
        .div-explore .bot{
           margin:10px 10px 10px 10px;
           border: 1px solid #9BCCE0;
           background-color: #DDF0D5;
        }
        .div-clear {
          clear: both;
        }

    `]
})

export class HomeComponent { }

The HomeComponent will serve as our master page for displaying the list of data from various components. We added the element to display the "Latest Discussion". Also, we’ve added the element to display the "What’s New?" and "Top Places to Visit" list with a standard class attribute to uniquely identify each one of them. The class attribute defined in the element will be used as the target of a property binding: We were referring to the @Input() decorator attribute which we defined in the PlaceListComponent.

We also use the class attribute to uniquely define a sets of styles for each elements: As we can see from the Angular2 template's styles section above.

Again, will be defining the directives and providers that the Component needs within our app.module.ts.

Enabling Client-Side Routing

At this point, we’ve done implementing the needed components for our Angular2 application yet it has a few major issues. The navigation we’ve setup in our master-detail components will not work since the URL routes defined from our components doesn’t exist yet. Also, we need to configure our app to enable client-side routing. It’s time for us to connect the dots in the picture to achieve what we expect.

Creating the App.Routes

Create a new TypeScript file under "/Scripts/app" folder and name it as "app.routing.ts". Copy the following code below:

import {ModuleWithProviders} from "@angular/core";  
import {Routes, RouterModule} from "@angular/router";

import { HomeComponent } from "./components/home/home.component";  
import { LoungeDetailComponent } from "./components/lounge/lounge-detail.component";  
import { PlaceDetailComponent } from "./components/explore/place-detail.component";

const routes: Routes = [  
    {
        path: '',
        redirectTo: '/home',
        pathMatch: 'full'
    },
    {
        path: 'home',
        component: HomeComponent
    },
    {
        path: 'lounge/:id',
        component: LoungeDetailComponent
    },
    {
        path: 'explore/:id',
        component: PlaceDetailComponent
    }
];

export const AppRoutingProviders: any[] = [  
];

export const AppRouting: ModuleWithProviders = RouterModule.forRoot(routes);  

Let’s see what we just did there.

Just like the other regular components, we need to import the module/directives that the component needs. It this case, we’ve imported the "@angular/router" to make use of the Angular 2 router interface: Specifically, we need to use and declare routes by defining a Routes and define an export to add the router to our bootstrap.

The Routes is an array of route definitions. A route is typically composed of two main parts: The path and component. From the code above, we have defined some routes that we need for this particular series. The first route indicates our default component that is targeting to our Home component. The rest of the routes are pointing to our components we created earlier.

Modifying the AppModule File

Let’s modify the app.module.ts file to make the routes available to any components that needs it. We are also going to add the needed directives and providers that our previously created components expect.

Here's the udpated app.module.ts:

///<reference path="../../typings/index.d.ts"/>
import {NgModule} from "@angular/core";  
import {BrowserModule} from "@angular/platform-browser";  
import {HttpModule} from "@angular/http";  
import {RouterModule}  from "@angular/router";  
import {FormsModule} from "@angular/forms";  
import "rxjs/Rx";

import {AppComponent} from "./components/app.component";  
import {HomeComponent} from "./components/home/home.component";  
import {LoungeListComponent} from "./components/lounge/lounge-list.component";  
import {LoungeDetailComponent} from "./components/lounge/lounge-detail.component";  
import {PlaceListComponent} from "./components/explore/place-list.component";  
import {PlaceDetailComponent} from "./components/explore/place-detail.component";

import {AppRouting} from "./app.routing";  
import {AppService} from "./services/app.service";

@NgModule({
    // directives, components, and pipes
    declarations: [
        AppComponent,
        HomeComponent,
        LoungeListComponent,
        LoungeDetailComponent,
        PlaceListComponent,
        PlaceDetailComponent
    ],
    // modules
    imports: [
        BrowserModule,
        HttpModule,
        FormsModule,
        RouterModule,
        AppRouting
    ],
    // providers
    providers: [
        AppService
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { }  

Modifying the App Component

Now, we need to add an anchor tag to the template which, when clicked, triggers navigation to the Components. To do that, open "app.component.ts" file and update the code so it would look something like this:

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


@Component({
    selector: "angularjs2demo",
    template: `
        <h1>{{title}}</h1>
        <h3>{{subTitle}}</h3>
            <div class="menu">
                <a class="home" [routerLink]="['/home']">Home</a> |
            </div>
        <router-outlet></router-outlet>
    `
})


export class AppComponent {  
    title = "The Backpackers' Lounge";
    subTitle = "For geeks who want to explore nature beyond limits.";
}

The [routerLink] binding in the anchor tag tells the router where to navigate when a user clicks a specific link. The tag acts as the placeholder element for the component that will be injected into the page DOM by the routing engine whenever the associated route is visited. Notice that we placed it right after our new navigation menu, so it will keep its position as the page content changes.

Adding the base Tag

Open "wwwroot/index.html" and add <base href="/"> at the very top within the <head> section. Our updated index.html file should now look something like this:

<html>  
<head>  
    <base href="/">
    <title>ASP.NET Core with Angular 2 RC Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Step 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="js/shim.min.js"></script>
    <script src="js/zone.js"></script>
    <script src="js/Reflect.js"></script>
    <script src="js/system.src.js"></script>

    <!-- Angular2 Native Directives -->
    <script src="/js/moment.js"></script>

    <!-- Step 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>  
<!-- Step 3. Display the application -->  
<body>  
    <!-- Application PlaceHolder -->
    <angularjs2demo>Please wait...</angularjs2demo>
</body>  
</html>  

We need to set the base tag as it will tell the routing engine how to compose all of the upcoming navigation URLs our app will eventually have. For details, see: Router Base HREF

Rewriting

The last and final step is to handle rewrites in our web.config file. We need to tell the Web Server to rewrite all routing URLs, including the root one, to the index.html file by adding the following lines to the <system.webServer> section of web.config:

<rewrite>  
      <rules>
        <rule name="Angular2 pushState routing" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            <add input="{REQUEST_FILENAME}" pattern=".*\.[\d\w]+$" negate="true" />
            <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
          </conditions>
          <action type="Rewrite" url="/index.html" />
        </rule>
      </rules>
</rewrite>  

By implementing the rules above, we’re basically asking our Web Server to re-address any incoming request to the /index.html file, with the sole exception of those pointing to:

  • Any existing file (to preserve references to actual .js, .css, .pdf, image files & more).
  • Any existing folder (to preserve references to actual, potentially-browsable and/or Angular-unrelated subfolders).
  • Anything within the /api/ folder (to preserve any call to our Web API Controllers). Running the App

Now try to compile and rebuild your project and make sure that your gulp task is running. Hit F5 and you should be able to see something like this as the output:

Notice that the URL changes after clicking an item from the list and routes it to the corresponding details view. You can also see that the data changes automatically as you type: That’s how the two-way data binding works in Angular2.

Tips

If your changes do not reflect on the browser, then it’s probably a caching issue. To resolve that, add the following configuration below under <system.webServer> element in your web.config file:

<caching enabled="false"/>  

For ASP.NET Core applications in VStudio 2015, configuring TypeScript is a bit challenging. The tsconfig.json will not be honored because the project file (.xproj) takes precedence. So if you are getting the following TypeScript error or warnings below:

  • TS1219 Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option to remove this warning
  • TS2307 Cannot find module '@angular/core'.

Until the tooling around TypeScript improves, you can configure TypeScript manually:

Step 1: Right-click project, and Unload Project
Step 2: Right-click the unloaded project, and Edit the .xproj file
Step 3: Add a PropertyGroup node, under the Project node:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">  
    <TypeScriptTarget>ES5</TypeScriptTarget>  
    <TypeScriptJSXEmit>None</TypeScriptJSXEmit>  
    <TypeScriptCompileOnSaveEnabled>True</TypeScriptCompileOnSaveEnabled>  
    <TypeScriptNoImplicitAny>False</TypeScriptNoImplicitAny>  
    <TypeScriptModuleKind>System</TypeScriptModuleKind>  
    <TypeScriptRemoveComments>False</TypeScriptRemoveComments>  
    <TypeScriptOutFile />  
    <TypeScriptOutDir />  
    <TypeScriptGeneratesDeclarations>False</TypeScriptGeneratesDeclarations>  
    <TypeScriptNoEmitOnError>True</TypeScriptNoEmitOnError>  
    <TypeScriptSourceMap>True</TypeScriptSourceMap>  
    <TypeScriptMapRoot />  
    <TypeScriptSourceRoot />  
    <TypeScriptExperimentalDecorators>True</TypeScriptExperimentalDecorators>  
    <TypeScriptEmitDecoratorMetadata>True</TypeScriptEmitDecoratorMetadata>  
</PropertyGroup>  

Step 4: Right-click the unloaded project and Reload Project
Step 5: Re-build project

Summary

We’ve learned a lot of things in this part of the series, starting from upgrading to Angular2 RC6 down to creating a data-driven Angular2 app from scratch within the context of ASP.NET Core. We have learned how to create and communicate with Web API in our Angular2 app. We’ve also learned the basics of Angular2 Master-Detail implementation, Two-Way Data binding and Routing.

You can view the source code from my github repo at: https://github.com/proudmonkey/TheBackpackerLounge

Stay tuned for my next article.