Sunday, November 20, 2016

Continuous deployment of angular-cli Angular 2 app to Azure thru GitHub

In this post, I will show you how to deploy an Angular 2 application on GitHub to Azure. The application will first be pushed to GitHub then we will configure Azure to automatically sync changes from GitHub. This may not be the quickest way to do it, but it does work.

The following are prerequisites:
  • node.js is installed
  • angular-cli is installed
  • git is installed on your computer
  • you have a GitHub account
  • you have an Azure subscription

Creating a sample Angular 2 app

The first step is to create an application named ng2_to_azure. Type the following within a working directory in a terminal windows:

ng new ng2_to_azure

Once the above command completes, you will find the following directory and file structure has been created in a new directory named ng2_to_azure:
image

Change to the newly created directory with:

cd ng2_to_azure

Start the Angular 2 app with this command:

ng serve

If you point your browser to http://localhost:4200. You should see the following web page:

image

Creating production version of Angular 2 app

If you open the .gitignore text file in an editor, you will notice that the /dist directory is excluded. Comment out that line by adding a # so that it looks like this:

image

Save the .gitignore file and exit your editor. We will next produce the production version of our app. This is done by executing the following build command while in the root ng2_to_azure directory:

ng build --prod --aot

The above command transpiles all the TypeScript files JavaScript files and puts the results in the /dist folder. The aot switch builds using ahead-of-time compilation. Have a peek at the contents of the /dist folder:

image

Notice that the main page is index.html. Assuming that your web server is still running, point your browser to http://localhost:4200/dist. The production version of your application will display in the browser:

image

Adding GitHub source control to your application

Go to http://github.com and login into your account.

image

On the right-side, beside "Your repositories", click on the green "New Repository" button.

image

Enter ng2_to_azure for repository name, then click on the green "Create repository" button.

image

We will follow some of the instructions highlighted above. Go to a command-line in the ng2_to_azure directory on your computer. and type in the following commands:

git add .
git commit -m "first commit"
git remote add origin
https://github.com/{github user}/ng2_to_azure.git
git push -u origin master


Note: Make sure you enter your GitHub handle instead of {github user} in the URL above.
Once you have successfully completed the above, you can go back to GitHub in your browser and view all the files in your ng2_to_azure repository:

image

Note that the /dist folder is also contained in the repository.

Continuous deployment on azure

Go to http://portal.azure.com and login into you Azure account.

image

Click on the big + (New) on the top left-side.

image

Click Web App on the next blade.

image
  • Choose an "App name" that is sufficiently unique when combined with domain azurewebsites.net
  • Choose your subscription
  • For "Resource Group" either create a new or use an existing one
  • While configuring "App Service plan/Location", you get to choose which data center you want.
Finally, click on the blue Create button at the bottom of the blade. Once your web app is created, it will appear in the list of your "App Services".

image

Click on the web app you just created.

image

Enter "deploy" in the filter field at the top then select "Deployment Options".

image

On the next blade, click on "Choose Source".

image

Click on GitHub. If this is the first time you connect to GitHub from Azure, you will be asked for your credentials. Once your GitHub credentials are known to Azure then you will see a blade that looks like this:

image

Click on "Choose project" to select the appropriate GitHub repository that you wish to connect Azure with.

image

Click on the Angular 2 repository you previously setup on GitHub. Back in the "Deployment source" blade, click on the blue OK button at the bottom of the blade. If you click on "Deployment options" again, you will determine that Azure is building your app with the following message:

image

Finally, when the build process is complete, you should see the following:

image

Now let us point to our application in the browser. In my case, I tried to access http://ng2azure.azurewebsites.net and this is the response I got:

image

One can guess that the web server found no default page in the root of the web application. To prove my point, we can find out what files we have in the root of the web site. Go back to the Azure portal in your browser and enter the word “Tools” in your filter field:

image

Click on Console. This will take you into the Azure terminal window. Type "dir" to see what is in the root folder.

image

It is obvious that there is no welcome page in the root of the web application. What we need to do is educate Azure that the /dist directory is indeed the root of our web application.

Enter the word "application" into the filter field.

image

Click on Application settings. Under "App settings", add the following

key = Project
value = ./dist

image

Do not forget to click on Save at the top.

image

To get the new configuration to take effect, we must trigger a new deployment. Go back to "Deployment options" and click on Sync so a new deployment is initiated.

image

Once the new deployment is completed, click on your fully qualified web address on azure and you should see that your application is indeed working.

image

If you have a better way of deploying an Angular 2 application to Azure, I would appreciate your letting me know through a comment on this post.

ASP.NET Core MVC + Web API app with EF Core 1.0 & SQLite

This tutorial uses Visual Studio 2015 and ASP.NET Core 1.0.

My intention is to give you a practical introduction into developing ASP.NET Core 1.0 MVC and Web API apps with the SQLite database as an alternative to traditional SQL Server.

A useful utility that comes in handy when working with the SQLite database is SQLiteStudio. Download SQLiteStudio from: http://sqlitestudio.pl/?act=download. Extract the ZIP file and place contents in a separate folder. Run SQLiteStudio.exe.

We will build an ASP.NET Core 1.0 app that uses the following Student entity:

image_thumb1

ASP.NET Core 1.0 MVC project

Create a new ASP.NET Core app in Visual Studio 2015:
  • File >> New >> Project
  • Templates >> Visual C# >> Web
image
  • Select “ASP.NET Web Application (.NET Core)”, name the project SQLiteWeb then click on OK.
image
  • Select “Web Application”, uncheck “Host in the cloud”, then click on OK.

Open the global.json and note the runtime version that will be used by the application:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003131"
  }
}

The above indicates that the version of the runtime that is being used is 1.0.0-preview2-003131.
Dependencies are added to the project.json file. Open this file and have a peek at the dependencies section:
"dependencies": {
  "Microsoft.NETCore.App": {
    "version": "1.0.1",
    "type": "platform"
  },
  "Microsoft.AspNetCore.Diagnostics": "1.0.0",
  "Microsoft.AspNetCore.Mvc": "1.0.1",
  "Microsoft.AspNetCore.Razor.Tools": {
    "version": "1.0.0-preview2-final",
    "type": "build"
  },
  "Microsoft.AspNetCore.Routing": "1.0.1",
  "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
  "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
  "Microsoft.AspNetCore.StaticFiles": "1.0.0",
  "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
  "Microsoft.Extensions.Configuration.Json": "1.0.0",
  "Microsoft.Extensions.Logging": "1.0.0",
  "Microsoft.Extensions.Logging.Console": "1.0.0",
  "Microsoft.Extensions.Logging.Debug": "1.0.0",
  "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
  "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0"
},
Build the application then run it by hitting CTRL F5 to see what it looks like in your browser.

image

This runs your application in IIS Express. Close your browser.

When working with ASP.NET Core 1.0, you will need to go to the command-line interface frequently. Add a command-prompt extension to make it easier. Click on Tools >> Extensions and Updates…

image

Find an extension named “Open Command Line” as shown below.

image

If you have not installed it already, install the above extension.

In solution explorer, right-click on the SQLiteWeb node the choose “Open Command Line” >> “Default (cmd)

image

This opens a regular operating system terminal window. Try out some important .NET Core 1.0 commands.
  1. dotnet --help – this gives you a list of common commands
  2. dotnet restore – restore dependencies specified in the .NET project
  3. dotnet build - Builds a .NET project
  4. dotnet run --help – provides help information about the run command
  5. dotnet new --type --help – shows the types of templates that can be scaffolded. At the time of writing these are: Console, Web, Lib, xunittest.
  6. dotnet new --type web – scaffolds a web template in the current directory
Execute the following command in order to run your web application using another cross-platform web server called Kestrel on port 5000, instead of IIS Express.

dotnet run

Note the following message:

Project SQLiteWeb (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
Hosting environment: Production
Content root path: D:\scrap\_4976\SQLiteWeb\src\SQLiteWeb
Now listening on:
http://localhost:5000
Application started. Press Ctrl+C to shut down.

Point your browser to http://localhost:5000. You will see the exact same page as before.
Close your browser, close the command-line terminal window and return to Visual Studio 2015.
We will need to add some dependencies in order to use SQLite with Entity Framework Core . Add these dependencies to the project.json file:

"Microsoft.EntityFrameworkCore.Sqlite": "1.0.1",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"

Add the following to the tools section in the project.json file:
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"

Class Library Project

It is good practice to place all your data models inside a Models folder. Therefore, add a folder called Models. Inside of the Models folder, add a class file named Student.cs. Use the following code for the class file:

public class Student {
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string School { get; set; }
  public DateTime StartDate { get; set; }
}


Also, to the Models folder, add another class file named SchoolContext.cs with the following class definition code:

public class SchoolContext : DbContext {
  public SchoolContext(DbContextOptions<SchoolContext> options) :base(options)
        { }

  public DbSet<Student> Students { get; set; }
}


We will need to add a connection string for the SQLite database in the SQLiteWebAPI project:
  • Open the appsettings.json file in the SQLiteWebAPI project
  • Add the following section after the Logging block:
"Data": {
  "DefaultConnection": {
    "ConnectionString": "Data Source=school-db.sqlite"
  }
}
We will need to add both Entity Framework and SQLite to the project’s Startup.cs class:
  • Open the Startup.cs file.
  • Add the following code to the ConfigureServices() method just before services.AddMvc();
var connection = Configuration["Data:DefaultConnection:ConnectionString"];
services.AddDbContext<SchoolContext>(options => options.UseSqlite(connection));


The above reads the connection string from the appsettings.json file and passes it on to the SchoolContext class.

Seed Data

Before we carry out code first migrations, let us first create some seed data:
  • In the Models folder, create a class named DummyData.cs.
  • Add the following Initialize() method code inside the DummyData class:
public static void Initialize(SchoolContext db) {
  if (!db.Students.Any()) {
    db.Students.Add(new Student {
      FirstName = "Bob",
      LastName = "Doe",
      School = "Engineering",
      StartDate = Convert.ToDateTime("2015/09/09")
    });
    db.Students.Add(new Student {
      FirstName = "Ann",
      LastName = "Lee",
      School = "Medicine",
      StartDate = Convert.ToDateTime("2014/09/09")
    });
    db.Students.Add(new Student {
      FirstName = "Sue",
      LastName = "Douglas",
      School = "Pharmacy",
      StartDate = Convert.ToDateTime("2016/01/01")
    });
    db.Students.Add(new Student {
      FirstName = "Tom",
      LastName = "Brown",
      School = "Business",
      StartDate = Convert.ToDateTime("2015/09/09")
    });
    db.Students.Add(new Student {
      FirstName = "Joe",
      LastName = "Mason",
      School = "Health",
      StartDate = Convert.ToDateTime("2015/01/01")
    });
    db.SaveChanges();
  }
}

To generate seed data, we will first inject the dependency “SchoolContext context” into the arguments of the Configure() method in Startup.cs. Next, we can make a call to seed the data at the bottom of the Configure() method with the following statement:
      
DummyData.Initialize(context);

Migrations

We are now ready to do some migrations:
  • Compile your application
  • Open a command terminal inside the src\SQLiteWeb folder
  • Add a migration to the project with the following ef command:
dotnet ef migrations add FirstMigration

Notice that class files are created in the Migrations folder.
  • We will then update the database with the following terminal command:
dotnet ef database update

At this point, there will be a file named school-db.sqlite in the src\SQLiteWeb\bin\Debug\netcoreapp1.0 folder. The data will not have been seeded yet because this happens when the application is actually run. 

Creating an MVC UI

Let us seed the data by running your web application in a browser. You should see the same page as we saw earlier. Let us create a UI so that we can see the seeded data.

  • Right-click on the Controllers folder and choose Add >> New Item… >> MVC Controller Class
  • Name the class file StudentController.cs.
  • Replace the class definition with the following code:
public class StudentController : Controller {
  private SchoolContext _context;

  public StudentController(SchoolContext context) {
      _context = context;
  }

  public IActionResult Index() {
      return View(_context.Students.ToList());
  }

  public IActionResult Create() {
      return View();
  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public IActionResult Create(Student student) {
      if (ModelState.IsValid) {
          _context.Students.Add(student);
          _context.SaveChanges();
          return RedirectToAction("Index");
      }

      return View(student);
  }

}

You’ll notice that the controller takes a SchoolContext as a constructor parameter. ASP.NET dependency injection will take care of passing an instance of SchoolContext into your controller.

The controller contains an Index action, which displays all blogs in the database, and a Create action, which inserts a new student into the database.

Let us add a view for the UI.
  • Create a new folder under Views named Student.
  • Right-click on the newly created Student folder and and select Add >> New Item…
  • Add an “MVC View Page” item and accept the default name index.cshtml.
  • replace the contents of the index.cshtml with the following code:
@model IEnumerable<SQLiteWeb.Models.Student>
@{
  ViewBag.Title = "Students";
}

<h2>@ViewBag.Title</h2>
<p>
  <a asp-controller="Student" asp-action="Create">Create New</a>
</p>

<table class="table">
  <tr>
    <th>Id</th>
    <th>First Name</th>
    <th>Last Name</th>
    <th>School</th>
    <th>Start Date</th>
  </tr>

  @foreach (var item in Model) {
  <tr>
      <td>
        @Html.DisplayFor(modelItem => item.Id)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.FirstName)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.LastName)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.School)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.StartDate)
      </td>
  </tr>
  }
</table>

  • Let us add a link to the Student controller on the main page of our application. Open _Layout.cshtml under Views/Shared.
  • Paste the following markup in the navigation section around line 36:
<li><a asp-area="" asp-controller="Student" asp-action="Index">Student</a></li>
  • Run the application then click on the Student link. You should see the dummy data that we created.
image
The “Create New” link is currently broken. We need to develop the UI for adding new students
  • Create a “MVC View Page” item create.cshtml under Views/Student.
  • Replace the contents of create.cshtml with the following markup:
@model SQLiteWeb.Models.Student
@{
  ViewBag.Title = "New Blog";
}

<h2>@ViewData["Title"]</h2>
<form asp-controller="Student" asp-action="Create" method="post" class="form-horizontal" role="form">
  <div class="form-horizontal">
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
      <label asp-for="FirstName" class="col-md-2 control-label"></label>
      <div class="col-md-10">
        <input asp-for="FirstName" class="form-control" />
        <span asp-validation-for="FirstName" class="text-danger"></span>
      </div>
    </div>
    <div class="form-group">
      <label asp-for="LastName" class="col-md-2 control-label"></label>
      <div class="col-md-10">
        <input asp-for="LastName" class="form-control" />
        <span asp-validation-for="LastName" class="text-danger"></span>
      </div>
    </div>
    <div class="form-group">
      <label asp-for="School" class="col-md-2 control-label"></label>
      <div class="col-md-10">
        <input asp-for="School" class="form-control" />
        <span asp-validation-for="School" class="text-danger"></span>
      </div>
    </div>
    <div class="form-group">
      <label asp-for="StartDate" class="col-md-2 control-label"></label>
      <div class="col-md-10">
        <input asp-for="StartDate" class="form-control" />
        <span asp-validation-for="StartDate" class="text-danger"></span>
      </div>
    </div>
    <div class="form-group">
      <div class="col-md-offset-2 col-md-10">
        <input type="submit" value="Create" class="btn btn-default" />
      </div>
    </div>
  </div>
</form>

  • Add a new student to the database by running your application and clicking on Student >> Create New.
image

The WebAPI Controller

Let us add a Web API Studentsapi controller to our projects.
  • In the Controllers folder, add a new “Web API Controller Class” named StudentapiController
  • Replace StudentapiController class definition with the following code:
[Route("api/[controller]")]
public class StudentapiController : Controller {
  private SchoolContext _context { get; set; }

  public StudentapiController(SchoolContext context) {
    _context = context;
  }

  // GET: api/student
  [HttpGet]
  public IEnumerable<Student> Get() {
    return _context.Students.ToList();
  }

  // GET api/studentapi/5
  [HttpGet("{id}")]
  public Student Get(int id) {
    return _context.Students.FirstOrDefault(s => s.Id == id);
  }

  // POST api/studentapi
  [HttpPost]
  public void Post([FromBody]Student student) {
    _context.Students.Add(student);
    _context.SaveChanges();
  }

  // PUT api/studentapi/5
  [HttpPut("{id}")]
  public void Put(int id, [FromBody]Student student) {
    _context.Students.Update(student);
    _context.SaveChanges();
  }

  // DELETE api/studentapi/5
  [HttpDelete("{id}")]
  public void Delete(int id) {
    var student = _context.Students.FirstOrDefault(t => t.Id == id);
    if (student != null) {
        _context.Students.Remove(student);
        _context.SaveChanges();
    }
  }
}


  • Hit CTRL-F5 on your keyboard and point your browser to /api/studentapi. You will see the seed data appearing as JSON in the browser:
image_thumb11

In subsequent blog posts I shall discuss

Saturday, November 19, 2016

Build an ASP.NET Core tag helper that consumes an Azure Web API RESTful service

Tag Helpers in ASP.NET Core allow you to create your own tags that fulfill a server-side purpose. In this tutorial, I will show you how to create a tag helper <cartoon-characters> that accesses a Web API service and displays contents in any razor view page.

The Web API service we will consume in this exercise is located at http://cartoonapi.azurewebsites.net/api/cartoon. It delivers the names of cartoon characters and their respective images.

1) To start with, create an ASP.NET Core Web application named TagHelperDemo in Visual Studio 2015.

2) We need to create a class that closely matches the nature of the Web API JSON object. Therefore, add the following CartoonCharacter class to a Models folder in your project:
public class CartoonCharacter {
  public string Name { get; set; }
  public string PictureUrl { get; set; }
}
3) Next, we will need to install the We API Client libraries. To that end, execute the following commands from within the Package Manager Console window in Visual Studio 2015:
Install-Package Microsoft.AspNet.WebApi.Client
Install-Package System.Runtime.Serialization.Xml
This should add the following dependencies to your project.json file:
"Microsoft.AspNet.WebApi.Client": "5.2.3",
"System.Runtime.Serialization.Xml":  "4.1.1"
4) Add the following tag to the bottom of Views/Home/About.cshtml:

<cartoon-characters></cartoon-characters>

5) Create a folder named TagHelpers and add to it a class file named CartoonCharactersTagHelper.cs. Have the class inherit from TagHelper and implement the ProcessAsync() method as follows:

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
}


We could have implemented a method Process() instead. However, in our case, it is appropriate to implement ProcessAsync() instead because we are about to make an async call to a remote service.

6) Add the following instance variable to the CartoonCharactersTagHelper class:
 private string baseUrl = "http://cartoonapi.azurewebsites.net";

7) Annotate the CartoonCharactersTagHelper class with the following:

[HtmlTargetElement("cartoon-characters")]
[HtmlTargetElement(Attributes = "cartoon-characters")]


The first annotation defines the tag <cartoon-characters> and the second defines the “cartoon-characters” attribute. This means that we have two different ways to produce the same output on a razor .cshtml view.

8) Add the following method to the CartoonCharactersTagHelper class:

async Task<IEnumerable<CartoonCharacter>> GetCartoonCharactersAsync() {
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri(baseUrl);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    IEnumerable<CartoonCharacter> cartoonCharacters = null;
    try {
        // Get all cartoon characters
        HttpResponseMessage response = await client.GetAsync("/api/cartoon");
        if (response.IsSuccessStatusCode) {
            cartoonCharacters = response.Content.ReadAsAsync<IEnumerable<CartoonCharacter>>().Result;
        }
    } catch (Exception e) {
        System.Diagnostics.Debug.WriteLine(e.ToString());
    }

    return cartoonCharacters;
}


The above code makes a request to the Web API service and returns an IEnumerable<CartoonCharacter> collection with the results.

9) Add the following code inside the ProcessAsync() method:

IEnumerable<CartoonCharacter> cartoonCharacters = await GetCartoonCharactersAsync();
string html = string.Empty;
html += "<table><tr><th>Name</th><th>Picture</th></tr>";
foreach (var item in cartoonCharacters) {
    string photoUrl = baseUrl + "/" + item.PictureUrl;
    html += "<tr>";
    html += "<td>" + item.Name + "</td>";
    html += "<td><img src='" + photoUrl + "' style='width: 50px' /></td>";
    html += "</tr>";
}
html += "<tr><table>";
output.Content.SetHtmlContent(html);


The above code creates a table with the collection of cartoon characters so that it can be displayed wherever the tag helper is used.

11) Register the tag name in the Views/_ViewImports.cshtml file by adding the following to the list of tags that are already there:

@addTagHelper "TagHelperDemo.TagHelpers.CartoonCharactersTagHelper, TagHelperDemo"

You may need to adjust the above names depending on what you called your app and/or your tag helper class.

11) Compile and run your application then click on About. You should see the following output:

image

If you inspect the table in your browser, you will see the following:

image

The above is using the tag and not the attribute. Edit About.cshtml and comment out “<cartoon-characters></cartoon-characters>” and put the following <div> tag with the cartoon-characters attribute underneath it:
<div cartoon-characters></div>

Your About.cshtml should now look like this:
@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>Use this area to provide additional information.</p>
@*<cartoon-characters></cartoon-characters>*@
<div cartoon-characters></div>
When you run your application. you should see the same output as before. If you inspect the table you will see the following HTML source:

image

This proves to us that you can either use tags or attributes with TagHelpers in ASP.NET Core.




Sunday, November 13, 2016

Build CRUD Angular 2 app that works with a Web API service

This is a follow up article on a previous post where I talked about using Consuming a RESful API with Angular 2 using angular-cli. In this article I will build an Angular 2 application that is more complete with routing and CRUD services that interact with a remote Web API service.

To proceed with this tutorial, it is assumed that you have the following applications already installed on your computer:
  • node.js
  • angular-cli
The first step is to create an application named ng2-flintstones. Type the following within a working directory in a terminal windows:

ng new ng2-flintones

Change to the newly created directory that houses your application with:

cd ng2-flintones

Start the application with this command:
ng serve

Point your browser to http://localhost:4200. You should see this web page:

image

Open the ng2-flinstones folder in Visual Studio Code. Open app.component.ts and modify the class definition so that it looks like this:

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  PersonId = 1;
  FirstName = "Fred";
  LastName = "Flintstone";
  Occupation = "Mining Manager";
  Gender = "M";
  Picture =  "
http://flintstones.zift.ca/images/flintstone/fred.png";
}
Modify app.component.html so that it has the following markup:
<p>Person ID: {{PersonId}}</p>
<p>First Name: {{FirstName}}</p>
<p>Last Name: {{LastName}}</p>
<p>Occupation: {{Occupation}}</p>
<p>Gender: {{Gender}}</p>
<p>Picture: <img src="{{Picture}}" alt="{{FirstName}} {{LastName}}" /></p>
Refresh your browser. You will be able to see the data that was initialized in the AppComponent class.
Instead of displaying literal data, let us create a CartoonCharacter class. Enter the following command in a terminal window while in the root folder of your application:

ng generate class CartoonCharacter

The above command produces a CartoonCharacter class file named cartoon-character.ts. Modify CartoonCharacter so that it looks like this:
export class CartoonCharacter {
  PersonId: number;
  FirstName: string;
  LastName: string;
  Occupation: string;
  Gender: string;
  Picture: string;
}
Replace the contents of app.component.ts with the following code:
import { Component } from '@angular/core';
import {CartoonCharacter} from './cartoon-character';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  character: CartoonCharacter = {
    PersonId: 1,
    FirstName: "Fred",
    LastName: "Flintstone",
    Occupation: "Mining Manager",
    Gender: "M",
    Picture:  "
http://flintstones.zift.ca/images/flintstone/fred.png",
  };
}

Now that we introduced a class, we can change app.component.html so that it contains the following markup:
<p>Person ID: {{ character.PersonId}}</p>
<p>First Name: {{ character.FirstName}}</p>
<p>Last Name: {{ character.LastName}}</p>
<p>Occupation: {{ character.Occupation}}</p>
<p>Gender: {{ character.Gender}}</p>
<p>Picture: <img src="{{ character.Picture}}"
  alt="{{ character.FirstName}} {{ character.LastName}}" /></p>
 
Refresh the web page. It should appear just like it did previously. The only difference is that we rendered data from an instance of a CartoonCharacter class.

Add the following HTML to the bottom of the app.component.html file:


<div>
    <p><label>First Name: </label><input value="{{ character.FirstName}}" placeholder="FirstName"></p>
    <p><label>Last Name: </label><input value="{{ character.LastName}}" placeholder="LastName"></p>
    <p><label>Occupation: </label><input value="{{ character.Occupation}}" placeholder="Occupation"></p>
    <p><label>Gender: </label><input value="{{ character.Gender}}" placeholder="M or F"></p>
    <p><label>Picture: </label><input value="{{ character.Picture}}" placeholder="Picture"></p>
</div>



Refresh the page and you will notice that some input boxes were added. Unfortunately, if you edit a data item in the text box it does not change above. We will fix that by implementing 2-way binding using [(ngModel)]. Replace app.component.html with the following:
<p>Person ID: {{character.PersonId}}</p>
<p>First Name: {{character.FirstName}}</p>
<p>Last Name: {{character.LastName}}</p>
<p>Occupation: {{character.Occupation}}</p>
<p>Gender: {{character.Gender}}</p>
<p>Picture: <img src="{{character.Picture}}"
  alt="{{character.FirstName}} {{character.LastName}}" /></p>

<div>
    <p><label>First Name: </label><input [(ngModel)]="character.FirstName" placeholder="FirstName"></p>
    <p><label>Last Name: </label><input [(ngModel)]="character.LastName" placeholder="LastName"></p>
    <p><label>Occupation: </label><input [(ngModel)]="character.Occupation" placeholder="Occupation"></p>
    <p><label>Gender: </label><input [(ngModel)]="character.Gender" placeholder="M or F"></p>
    <p><label>Picture: </label><input [(ngModel)]="character.Picture" placeholder="Picture"></p>
</div>
View page. When you change any of the fields the other data changes.
Create a new folder under app named data. In that folder create a file named dummy-data.ts with following content:

import {CartoonCharacter} from '../cartoon-character';
export const DUMMY_DATA: CartoonCharacter[] = [
  {"PersonId":1,"FirstName":"Fred","LastName":"Flintstone","Occupation":"Mining Manager","Gender":"M","Picture":"
http://flintstones.zift.ca/images/flintstone/fred.png"},
  {"PersonId":2,"FirstName":"Barney","LastName":"Rubble","Occupation":"Mining Assistant","Gender":"M","Picture":"
http://flintstones.zift.ca/images/flintstone/barney.png"},
  {"PersonId":3,"FirstName":"Betty","LastName":"Rubble","Occupation":"Nurse","Gender":"F","Picture":"
http://flintstones.zift.ca/images/flintstone/betty.png"},
  {"PersonId":4,"FirstName":"Wilma","LastName":"Flintstone","Occupation":"Teacher","Gender":"F","Picture":"
http://flintstones.zift.ca/images/flintstone/wilma.png"},
  {"PersonId":5,"FirstName":"Bambam","LastName":"Rubble","Occupation":"Baby","Gender":"M","Picture":"
http://flintstones.zift.ca/images/flintstone/bambam.png"},
  {"PersonId":6,"FirstName":"Pebbles","LastName":"Flintstone","Occupation":"Baby","Gender":"F","Picture":"
http://flintstones.zift.ca/images/flintstone/pebbles.png"},
  {"PersonId":7,"FirstName":"Dino","LastName":"Flintstone","Occupation":"Pet","Gender":"F","Picture":"
http://flintstones.zift.ca/images/flintstone/dino.png"}
]
Add to app.component.ts the following import statement:

import {DUMMY_DATA} from './data/dummy-data';

Add the following instance variable to the AppComponent class:

characters = DUMMY_DATA;


There is a form module that needs to be added to our project because we will be displaying and accepting data from a form. Therefore, find app.module.ts and add to it the following import code at the top of the file:

import { FormsModule} from '@angular/forms'

Also, add FormsModule to the imports array in the same app.module.ts file.

In order to display a collection of cartoon-characters, replace app.component.html with the following markup:

<table *ngIf="characters" border="1">
  <thead>
    <tr>
      <th>Person ID</th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Occupation</th>
      <th>Gender</th>
      <th>Picture</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let c of characters; let i=index; ">
      <td>{{c.PersonId}}</td>
      <td>{{c.FirstName}}</td>
      <td>{{c.LastName}}</td>
      <td>{{c.Occupation}}</td>
      <td>{{c.Gender}}</td>
      <td><img src="{{c.Picture}}"
        alt="{{c.FirstName}} {{c.LastName}}" /></td>
    </tr>
  </tbody>
</table>

<p>Person ID: {{character.PersonId}}</p>
<p>First Name: {{character.FirstName}}</p>
<p>Last Name: {{character.LastName}}</p>
<p>Occupation: {{character.Occupation}}</p>
<p>Gender: {{character.Gender}}</p>
<p>Picture: <img src="{{character.Picture}}"
        alt="{{character.FirstName}} {{character.LastName}}" /></p>

<div>
    <p><label>First Name: </label><input [(ngModel)]="character.FirstName" placeholder="FirstName"></p>
    <p><label>Last Name: </label><input [(ngModel)]="character.LastName" placeholder="LastName"></p>
    <p><label>Occupation: </label><input [(ngModel)]="character.Occupation" placeholder="Occupation"></p>
    <p><label>Gender: </label><input [(ngModel)]="character.Gender" placeholder="M or F"></p>
    <p><label>Picture: </label><input [(ngModel)]="character.Picture" placeholder="Picture"></p>
</div>



Now when you view the page once more you will notice that whenever you change a data item in the textbox it also changes above.

We want the user to select a cartoon-character from our list, and have the selected character appear in the details view. Modify the opening <tr> tag by inserting an Angular event binding to its click event, like this:

<tr *ngFor="let c of characters; let i=index;" (click)="onSelect(c)">

In the AppComponent class, replace the character declaration with:

selected: CartoonCharacter;

Now add an onSelect method to AppComponent that sets the selected property to the character the user clicked on.
onSelect(character: CartoonCharacter): void {
    this.selected= character;
}
We will be showing the selected character details in our template. Now, it is still referring to the old character property. Let’s fix the template to bind to the new selected property.

Hide the empty detail with *ngIf

When our app loads we see a list of characters, but a cartoon-character is not selected. The selected property is undefined. That’s why we'll see the following error in the browser’s console:
EXCEPTION: TypeError: Cannot read property 'PersonId' of undefined in [null]
Wrap the HTML cartoon character detail content of our template with a <div>. Then we add the *ngIf built-in directive and set it to the selected property of our component like this:
<div *ngIf="selected">
  <p>Person ID: {{selected.PersonId}}</p>
  <p>First Name: {{selected.FirstName}}</p>
  <p>Last Name: {{selected.LastName}}</p>
  <p>Occupation: {{selected.Occupation}}</p>
  <p>Gender: {{selected.Gender}}</p>
  <p>Picture: <img src="{{selected.Picture}}" alt="{{selected.FirstName}} {{selected.LastName}}" /></p>

  <div>
    <p><label>First Name: </label><input [(ngModel)]="selected.FirstName" placeholder="FirstName"></p>
    <p><label>Last Name: </label><input [(ngModel)]="selected.LastName" placeholder="LastName"></p>
    <p><label>Occupation: </label><input [(ngModel)]="selected.Occupation" placeholder="Occupation"></p>
    <p><label>Gender: </label><input [(ngModel)]="selected.Gender" placeholder="M or F"></p>
    <p><label>Picture: </label><input [(ngModel)]="selected.Picture" placeholder="Picture"></p>
  </div>
</div>

CSS

Let us change the background color for a row when it is selected. Add the following CSS into the app.component.css file:
.selected {
  background-color: #CFD8DC !important;
  color: darkblue;
}
In app.component.html, add the following to the <tr> tag that manages the iteration:

[class.selected]="c === selected"

The <tr> tag would look like this:

<tr *ngFor="let c of characters; let i=index;" (click)="onSelect(c)" [class.selected]="c === selected">

Separating the character details

Add a new details component with the following terminal command:

ng generate component CharacterDetail

This produces the following files:
src\app\character-detail\character-detail.component.css
src\app\character-detail\character-detail.component.html
src\app\character-detail\character-detail.component.spec.ts
src\app\character-detail\character-detail.component.ts
In character-detail.component.ts, modify the import statement so that it also imports “Input” as follows:

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

Move the whole <div> block that contains detail information from the app.component.html file to the character-detail.component.html file. Do a search and replace from “selected.” to “character.”. The contents of character-detail.component.html will look like this:

<div *ngIf="character">
  <p>Person ID: {{character.PersonId}}</p>
  <p>First Name: {{character.FirstName}}</p>
  <p>Last Name: {{character.LastName}}</p>
  <p>Occupation: {{character.Occupation}}</p>
  <p>Gender: {{character.Gender}}</p>
  <p>Picture: <img src="{{character.Picture}}" alt="{{character.FirstName}} {{character.LastName}}" /></p>

  <div>
    <p><label>First Name: </label><input [(ngModel)]="character.FirstName" placeholder="FirstName"></p>
    <p><label>Last Name: </label><input [(ngModel)]="character.LastName" placeholder="LastName"></p>
    <p><label>Occupation: </label><input [(ngModel)]="character.Occupation" placeholder="Occupation"></p>
    <p><label>Gender: </label><input [(ngModel)]="character.Gender" placeholder="M or F"></p>
    <p><label>Picture: </label><input [(ngModel)]="character.Picture" placeholder="Picture"></p>
  </div>
</div>

Add the cartoon-character property

Add a character property to the CharacterDetailComponent component class:
@Input()
character: CartoonCharacter;
Also, import the CartoonCharacter class with the following import statement.

import {CartoonCharacter} from '../cartoon-character';

The CharacterDetailComponent class must be told what cartoon-character to display by the parent AppComponent class. This will be placed in the <app-character-detail> tag as follows:

<app-character-detail [character]="selected"></app-character-detail>

We will update app.component.html with this later. Meantime, annotate the character property with the @Input decorator that we imported earlier.

Refresh the AppComponent

Open app.component.html and replace the <div> tag that is responsible for displaying selected data with the following:

<app-character-detail [character]="selected"></app-character-detail>

The app.component.html file should now look like this:
<table *ngIf="characters" border="1">
  <thead>
    <tr>
      <th>Person ID</th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Occupation</th>
      <th>Gender</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let c of characters; let i=index;" (click)="onSelect(c)" [class.selected]="c === selected">
      <td>{{c.PersonId}}</td>
      <td>{{c.FirstName}}</td>
      <td>{{c.LastName}}</td>
      <td>{{c.Occupation}}</td>
      <td>{{c.Gender}}</td>
    </tr>
  </tbody>
</table>
<app-character-detail [character]="selected"></app-character-detail>
Check the web application in your browser and make sure that it works and behaves just as it did before we separated out the details about our characters.

Creating a CartoonCharacter service

Next, we will create a cartoon-character service that can be used from a multitude of components. To this end, execute the following instruction from a terminal window while in the root folder:

ng generate service CartoonCharacter

This produces the following files:
src\app\cartoon-character.service.spec.ts
src\app\cartoon-character.service.ts
Add an empty getCartoonCharacters() method to the CartoonCharacterService class as follows:

getCartoonCharacters(): void { }

Add the following import commands to the new service cartoon-character.service.ts file:
import {DUMMY_DATA} from './data/dummy-data';
import {CartoonCharacter} from './cartoon-character'
In app.component.ts, change ‘characters = DUMMY_DATA;’ to ‘characters: CartoonCharacter[];’ and delete the DUMMY_DATA import statement. The app.component.ts should look like this:

import { Component } from '@angular/core';
import {CartoonCharacter} from './cartoon-character';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  /*
  character: CartoonCharacter = {
    PersonId: 1,
    FirstName: "Fred",
    LastName: "Flintstone",
    Occupation: "Mining Manager",
    Gender: "M",
    Picture:  "
http://flintstones.zift.ca/images/flintstone/fred.png",
  };
  */

  selected: CartoonCharacter;
  characters: CartoonCharacter[];
  onSelect(character: CartoonCharacter): void {
    this.selected = character;
  }
}


In the CartoonCharacterService class, replace the getCartoonCharacters() method with the following code:
getCartoonCharacters(): CartoonCharacter[] {  
  return DUMMY_DATA;
}
The cartoon-character.service.ts file now should look like this:
import { Injectable } from '@angular/core';
import {DUMMY_DATA} from './data/dummy-data';
import {CartoonCharacter} from './cartoon-character'

@Injectable()
export class CartoonCharacterService {

  constructor() { }
  getCartoonCharacters(): CartoonCharacter[] {  
    return DUMMY_DATA;
  }
}

Using the CartoonCharacter service

We will be using the new service in app.component.ts. Back in that file, import the service with the following statement:

import {CartoonCharacterService} from './cartoon-character.service';

We do not want to instantiate a new instance of CartoonCharacterService. Instead, we will use dependency injection. Add the following constructor to the AppComponent class:

constructor(private cartoonService: CartoonCharacterService) { }

Add the following providers array to @Component for the AppComponent class:
providers: [CartoonCharacterService]
Add the following method to AppComponent:
getCartoonCharacters(): void {
  this.characters = this.cartoonService.getCartoonCharacters();
}
Instead of making a call to getCartoonCharacters() from within the constructor of AppComponent, we will make a call when the component gets initialized. This is best done in the ngOnInit() method.

To do this we need to import OnInit and implement OnInit. We will then make a call to getCartoonCharacters() from within the ngOnInit() method. Here’s what app.component.ts should now look like:

import { Component, OnInit } from '@angular/core';
import {CartoonCharacter} from './cartoon-character';
import {CartoonCharacterService} from './cartoon-character.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [CartoonCharacterService]
})
export class AppComponent implements OnInit {
  selected: CartoonCharacter;
  characters: CartoonCharacter[];

  constructor(private cartoonService: CartoonCharacterService) { }
  onSelect(character: CartoonCharacter): void {
    this.selected = character;
  }

  getCartoonCharacters(): void {
    this.characters = this.cartoonService.getCartoonCharacters();
  }

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


Our application should be working fine now.

There is one problem, though. Our service is not making async calls. We will change that. Change the getCartoonCharacters() method in the CartoonCharacterService class to the following:
getCartoonCharacters(): Promise<CartoonCharacter[]> {  
  return Promise.resolve(DUMMY_DATA);
}
Since this returns a promise, we will need to change the way this method is called. Back in AppComponent class, change the getCartoonCharacters() method to the following:
getCartoonCharacters(): void {
this.cartoonService.getCartoonCharacters()
  .then(characters => this.characters = characters);
}

Routing around our app

We will build a menu system with links allowing us to choose what it is we want to do. Now, we only have one page. This is not very realistic for a larger project. Our revised app should display a shell with a choice of views (Dashboard and Cartoon Characters) and then default to one of them. The first task is to move the display of cartoon-characters out of AppComponent and into its own CartoonCharacterComponent.

Let us create a new component named CartoonCharacterComponent with the following terminal command:

ng generate component CartoonCharacter

The following files get generated:
src\app\cartoon-character\cartoon-character.component.css
src\app\cartoon-character\cartoon-character.component.html
src\app\cartoon-character\cartoon-character.component.spec.ts
src\app\cartoon-character\cartoon-character.component.ts
Since AppComponent is doing the work that we want CartoonCharacterComponent to do, let us simply copy the contents of AppComponent to CartoonCharacterComponent:
|
Copy Contents from … to …
app.component.html cartoon-character.html
app.component.css cartoon-character.css
app.component.ts cartoon-character.ts
Also:
  • change the app-root selector in copied contents of cartoon-character.component.ts to cartoon-character-component
  • delete the providers array line – providers: [CartoonCharacterService]
  • adjust the filenames in cartoon-character.component.ts to match the actual .html and .css files
  • app.component.html should contain only the following markup:
<cartoon-character-component></cartoon-character-component>

Make sure the content of app.component.ts has the following code’:

import { Component } from '@angular/core';
import { CartoonCharacterService } from './cartoon-character.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [CartoonCharacterService]
})
export class AppComponent {
  title = 'The Flintstones';
}

Check out the application, it should be working fine just like before.

Routing

The next big task is to add routes and links to our main landing page. Open app.module.ts in your editor and add the following import statement:

import { RouterModule } from '@angular/router';

Define our first route by adding the following to the imports array:
RouterModule.forRoot([
  {
    path: 'characters',
    component: CartoonCharacterComponent
  }
])
app.module.ts now looks like this:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CharacterDetailComponent } from './character-detail/character-detail.component';
import { CartoonCharacterComponent } from './cartoon-character/cartoon-character.component';

import { RouterModule } from '@angular/router';
@NgModule({
  declarations: [
    AppComponent,
    CharacterDetailComponent,
    CartoonCharacterComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
      {
        path: 'characters',
        component: CartoonCharacterComponent
      }
    ])
  ],
  providers: [],
  bootstrap: [AppComponent],
 
})
export class AppModule { }


The route definition has the following parts:
  • path: the router matches this route's path to the URL in the browser address bar (characters).
  • component: the component that the router should create when navigating to this route (CartoonCharacterComponent).

Router Outlet

If we paste the path, /characters, into the browser address bar, the router should match it to the characters route and display the CartoonCharacterComponent. But where?

We must tell it where by adding a <router-outlet> element to the bottom of the template. The router displays each component immediately below the <router-outlet> as we navigate through the application.

Edit the app.component.html file. Replace <cartoon-characters></cartoon-characters> with the following markup:
<nav>
    <a routerLink="/characters">Cartoon Characters</a>
</nav>
<router-outlet></router-outlet>
When you run the application, you will see a link as follows:

image

When you click on the “Cartoon Characters” link, two things happen:
  1. The address line in your browser changes to: http://localhost:4200/characters
  2. The contents of CartoonCharacterComponent are injected into <router-outlet></router-outlet>

Adding another dashboard link

Create another component named DashboardComponent by executing the following in a terminal window:

ng generate component Dashboard

This causes the following files to be created for us:
src\app\dashboard\dashboard.component.css
src\app\dashboard\dashboard.component.html
src\app\dashboard\dashboard.component.spec.ts
src\app\dashboard\dashboard.component.ts
Add another route to app.module.ts as shown below:
{
  path: 'dashboard',
  component: DashboardComponent
},
Next, let us add one more link to app.component.html. Our latest iteration of app.component.html looks like this:
<h1>{{title}}</h1>
<nav>
  <a routerLink="/dashboard">Dashboard</a>
    <a routerLink="/characters">Cartoon Characters</a>
</nav>
<router-outlet></router-outlet>
At this point, when you run the application this is what you should see:

image

Of course, we still need to develop our DashboardComponent class. Modify dashboard.component.ts so that it looks like this:

import { Component, OnInit } from '@angular/core';
import {CartoonCharacter} from '../cartoon-character';
import {CartoonCharacterService} from '../cartoon-character.service';
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
  characters: CartoonCharacter[];

  constructor(private cartoonService: CartoonCharacterService) { }
  ngOnInit() {
    this.cartoonService.getCartoonCharacters()
      .then(results => this.characters = results.slice(0, 4));
 
}
}

Also, modify dashboard.component.html so that it looks like this:

<h3>The Flintstones Family</h3>
<p *ngFor="let c of characters" class="col-lg-4">
      {{c.FirstName}} {{c.LastName}}
</p>
Refresh the browser and see four cartoon characters in the new dashboard as shown below:

image

Click on “Cartoon Characters” and you will see our table with details.

Routing to our Cartoon Character details

One refinement that is overdue is to navigate to a details page if the user clicks on a character on our dashboard.

We need to add an additional method to CartoonCharacterService that retrieves a single record by id. Open CartoonCharacterService and add a getCartoonCharacterById() method that filters the cartoon-characters list from getCartoonCharacters() by id:

getCartoonCharacterById(id: number): Promise<CartoonCharacter> {
  return this.getCartoonCharacters()
    .then(result => result.find(character => character.PersonId === id));
}


We'll add a route to the CharacterDetailComponent in app.module.ts where our other routes are configured. Add the following route definition to app.module.ts:
{
  path: 'detail/:id',
  component: CharacterDetailComponent
},
The colon (:) in the path indicates that :id is a placeholder to be filled with a specific cartoon character id when navigating to the CharacterDetailComponent.

The revised CharacterDetailComponent should take the id parameter from the params observable in the ActivatedRoute service and use the CartoonCharacterService to fetch the cartoon character with that id.

Add the following import statements to character-detail.component.ts:

import { ActivatedRoute, Params }   from '@angular/router';
import { Location }                 from '@angular/common';
import { CartoonCharacterService } from '../cartoon-character.service';


Let's have the ActivatedRoute, CartoonCharacterService and Location services injected into the constructor, saving their values in private fields:
constructor(
  private cartoonService: CartoonCharacterService,
  private route: ActivatedRoute,
  private location: Location
) { }
Inside the ngOnInit lifecycle method, we use the params observable to extract the id parameter value from the ActivatedRoute service and use the CartoonCharacterService to fetch the cartoon-character with that id.
ngOnInit() {
  this.route.params.forEach((params: Params) => {
    let id = +params['id'];
    this.cartoonService.getCartoonCharacterById(id)
      .then(result => this.character = result);
  });
}
Notice how we extract the id by calling the forEach method, which will deliver our array of route parameters. The cartoon character id is a number. Route parameters are always strings. So, we convert the route parameter value to a number with the JavaScript (+) operator.

Also, add this method to character-detail.component.ts so that we can go back:
goBack(): void {
  this.location.back();
}
Add the following markup to character-detail.component.html so that it now looks like this:

<div *ngIf="character">
  <p>Person ID: {{character.PersonId}}</p>
  <p>First Name: {{character.FirstName}}</p>
  <p>Last Name: {{character.LastName}}</p>
  <p>Occupation: {{character.Occupation}}</p>
  <p>Gender: {{character.Gender}}</p>
  <p>Picture: <img src="{{character.Picture}}" alt="{{character.FirstName}} {{character.LastName}}" /></p>

  <div>
    <p><label>First Name: </label><input [(ngModel)]="character.FirstName" placeholder="FirstName"></p>
    <p><label>Last Name: </label><input [(ngModel)]="character.LastName" placeholder="LastName"></p>
    <p><label>Occupation: </label><input [(ngModel)]="character.Occupation" placeholder="Occupation"></p>
    <p><label>Gender: </label><input [(ngModel)]="character.Gender" placeholder="M or F"></p>
    <p><label>Picture: </label><input [(ngModel)]="character.Picture" placeholder="Picture"></p>
  </div>
  <button (click)="goBack()">Back</button>
</div>


When a user selects a cartoon-character on the dashboard, the app should navigate to the CharacterDetailComponent to view and edit the selected cartoon-character.

To achieve this effect, reopen the dashboard.component.html and replace the repeated <div *ngFor...> tags with <a> tags so that it looks like this:

<h3>The Flintstones Family</h3>
<a *ngFor="let c of characters"  [routerLink]="['/detail', c.PersonId]"  class="col-lg-4">
  <p>{{c.FirstName}} {{c.LastName}}</p>
</a>

Refresh the browser and select a cartoon-character from the dashboard; the app should navigate directly to that cartoon-character’s details.

imageimage

Clicking on the “Back” button will take you back in the history of the browser.

Refactor routes to a Routing Module

It is wise to put all of our routing rules in a separate file than app.module.ts so it does not get overly bloated. This is especially true if our application is large and has many routes. To this end, create an app-routing.module.ts file in the same folder as app.module.ts. Give it the following contents extracted from the AppModule class:

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent }   from './dashboard/dashboard.component';
import { CartoonCharacterComponent } from './cartoon-character/cartoon-character.component';
import { CharacterDetailComponent } from './character-detail/character-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard',  component: DashboardComponent },
  { path: 'detail/:id', component: CharacterDetailComponent },
  { path: 'characters',     component: CartoonCharacterComponent }
];

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


Next, let’s update app.module.ts so that it uses app-routing.module.ts. Add the following import statement to app.module.ts.

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

Also, change the imports array so it looks like this:

imports: [
  BrowserModule,
  FormsModule,
  HttpModule,
  AppRoutingModule
],


This would be the latest state of app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CharacterDetailComponent } from './character-detail/character-detail.component';
import { CartoonCharacterComponent } from './cartoon-character/cartoon-character.component';

import { RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';

import { AppRoutingModule } from './app-routing.module';
@NgModule({
  declarations: [
    AppComponent,
    CharacterDetailComponent,
    CartoonCharacterComponent,
    DashboardComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent],

})
export class AppModule { }


Our application functions properly and behaves as expected.


Select a Cartoon-Character in the CartoonCharacterComponent

Comment out the last line in cartoon-character.component.html so that it looks like this:

<!--
<app-character-detail [character]="selected"></app-character-detail>
-->


When the user selects a cartoon-character from the list, we don't go to the detail page. We show a mini-detail on this page instead and make the user click a button to navigate to the full detail page.

Add the following HTML fragment at the bottom of cartoon-character.component.html:

<div *ngIf="selected">
  <h2>
    {{selected.FirstName | uppercase}} {{selected.LastName | uppercase}} is favorite cartoon character.
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>


Update the Update the CartoonCharacterComponent class as follows:
    1. Import Router from the Angular router library with the following import statement: 
     
    import { Router } from '@angular/router';

    2. Inject the Router in the constructor (along with the CartoonCharacterService) as follow: 
     
    constructor(
      private cartoonService: CartoonCharacterService,
      private router: Router
    ) { }
    3. Implement gotoDetail() by calling the router.navigate method as shown below:
    gotoDetail(): void {
      this.router.navigate(['/detail', this.selected.PersonId]);
    }
Refresh your browser and checkout the new behavior of the application.

Styling:

Replace dashboard.component.css with the following:
label {
  display: inline-block;
  width: 3em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}
input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}
button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer; cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #ccc;
  cursor: auto;
}
Replace contents of cartoon-character.component.css with the following:
label {
  display: inline-block;
  width: 3em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}
input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}
button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer; cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #ccc;
  cursor: auto;
}
Replace contents of app.component.css with the following:
h1 {
  font-size: 1.2em;
  color: #999;
  margin-bottom: 0;
}
h2 {
  font-size: 2em;
  margin-top: 0;
  padding-top: 0;
}
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}
nav a:visited, a:link {
  color: #607D8B;
}
nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}
nav a.active {
  color: #039be5;
}
To set the global application styles, replace contents of styles.css with:
/* Master Styles */
h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}
h2, h3 {
  color: #444;
  font-family: Arial, Helvetica, sans-serif;
  font-weight: lighter;
}
body {
  margin: 2em;
}
body, input[text], button {
  color: #888;
  font-family: Cambria, Georgia;
}
/* . . . */
/* everywhere else */
* {
  font-family: Arial, Helvetica, sans-serif;
}

Navigate through the application and experience the refined look.

Working with real data

The data we are viewing is being served from a static array. We need to interact with a real remote service. We will work with a service located at http://flintstones.zift.ca/flintstones/ that delivers the same data as the static array used previously.

Let's convert getCartoonCharacters () in cartoon-character.ts to use HTTP. Add the following import statements to cartoon-character.service.ts:
import { Headers, Http, Response } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map';
Inject the http service into the service’s constructor with the following code:

constructor(private http: Http) { }

Add the following instance variable to the CartoonCharacterService class:

private BASE_URL = "http://flintstones.zift.ca/api/flintstones";

Change method getCartoonCharacters() in cartoon-character.service.ts to the following code:
getCartoonCharacters(): Promise<CartoonCharacter[]> {
  return this.http.get(this.BASE_URL)
   .toPromise()
   .then(response => response.json() as CartoonCharacter[])
   .catch(this.handleError);
}
The catch block calls a handleError() method. Add this handleError() method to the CartoonCharacterService class:

private handleError(error: any): Promise<any> {
  console.error('An error occurred', error); // for demo purposes only
  return Promise.reject(error.message || error);
}


Test the application and you will determine that it works just as before. The big difference is that it is indeed asynchronously retrieving data from a remote data source.

Updating data

The next challenge is to update data. Add the following code to the CartoonCharacterService class:

private headers = new Headers({'Content-Type': 'application/json'});
update(character: CartoonCharacter): Promise<CartoonCharacter> {
  const url = `${this.BASE_URL}/${character.PersonId}`;
  return this.http
    .put(url, JSON.stringify(character), {headers: this.headers})
    .toPromise()
    .then(() => character)
    .catch(this.handleError);
}


Add the following save button to the bottom of the character-detail.component.html file right after the back button:

&nbsp;&nbsp;&nbsp;&nbsp;<button (click)="save()">Save</button>

The save method persists cartoon-character data changes using the service’s update method, then navigates back to the previous view. Add this save() method to character-detail.component.ts:
save(): void {
  this.cartoonService.update(this.character)
    .then(() => this.goBack());
}
Refresh the browser and give it a try. Changes to cartoon-character data should now persist.

Add data

Add the following method to the CartoonCharacterService class:

create(newCartoonCharacter: CartoonCharacter): Promise<CartoonCharacter> {
  return this.http
    .post(this.BASE_URL, JSON.stringify(newCartoonCharacter), {headers: this.headers})
    .toPromise()
    .then(res => res.json().data)
    .catch(this.handleError);
}


Add this markup to cartoon-character.component.html:

<div>
  <p><label>First Name: </label><input [(ngModel)]="newCharacter.FirstName" placeholder="First Name"></p>
  <p><label>Last Name: </label><input [(ngModel)]="newCharacter.LastName" placeholder="Last Name"></p>
  <p><label>Occupation: </label><input [(ngModel)]="newCharacter.Occupation" placeholder="Occupation"></p>
  <p><label>Gender: </label><input [(ngModel)]="newCharacter.Gender" placeholder="M or F"></p>
  <p><label>Picture: </label><input [(ngModel)]="newCharacter.Picture" placeholder="Picture URL"></p>
  <button (click)="add(newCharacter);">
    Add
  </button>
</div>


Add the following to the CartoonCharacterComponent class:

newCharacter: CartoonCharacter = new CartoonCharacter();
add(newCartoonCharacter: CartoonCharacter): void {
  newCartoonCharacter.FirstName = newCartoonCharacter.FirstName.trim();
  newCartoonCharacter.LastName = newCartoonCharacter.LastName.trim();
  newCartoonCharacter.Occupation = newCartoonCharacter.Occupation.trim();
  newCartoonCharacter.Gender = newCartoonCharacter.Gender.trim();
  newCartoonCharacter.Picture = newCartoonCharacter.Picture.trim();
 
  if (!newCartoonCharacter) { return; }

  this.cartoonService.create(newCartoonCharacter)
    .then(newCartoonCharacter => {
      this.selected = null;
      this.router.navigate(['./dashboard']);
    });
}


Refresh the browser and add some sample cartoon-character data.

Deleting data

Add a new column to the table in cartoon-character.component.html and put the following button in a cell after {{c.Gender}}:

<button class="delete" (click)="delete(c); $event.stopPropagation()">x</button>

The entire table in cartoon-character.component.html will look like this:

<table *ngIf="characters" border="1">
  <thead>
    <tr>
      <th>Person ID</th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Occupation</th>
      <th>Gender</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let c of characters; let i=index;" (click)="onSelect(c)" [class.selected]="c === selected">
      <td>{{c.PersonId}}</td>
      <td>{{c.FirstName}}</td>
      <td>{{c.LastName}}</td>
      <td>{{c.Occupation}}</td>
      <td>{{c.Gender}}</td>
      <td><button class="delete" (click)="delete(c); $event.stopPropagation()">x</button></td>
    </tr>
  </tbody>
</table>


Add the following delete() method to the CartoonCharacterService class:
delete(id: number): Promise<void> {
  const url = `${this.BASE_URL}/${id}`;
  return this.http.delete(url, {headers: this.headers})
    .toPromise()
    .then(() => null)
    .catch(this.handleError);
}
Add this delete() method to the CartoonCharacterComponent class:

delete(delCharacter: CartoonCharacter): void {
  this.cartoonService
      .delete(delCharacter.PersonId)
      .then(() => {
        this.characters = this.characters.filter(c => c !== delCharacter);
        if (this.selected === delCharacter) { this.selected = null; }
      });
}


Refresh the browser and try the new delete the records that you created.

Conclusion

In this post, we have developed a well structured Angular 2 application that interacts with real data. If you and retrieve, add, update, and delete data then you are in a position to do some true an real world single page applications with Angular 2.

References:

https://angular.io/docs/ts/latest/tutorial/