The Practical Client
Your First TypeScript Angular 2 App
Here's how to use Angular2 and TypeScript to retrieve JSON objects from an ASP.NET MVC controller and populate your page with the results.
In a previous column, I walked through configuring an ASP.NET MVC project to use Angular2 with TypeScript. In the end, though, all my application did was display the hardcoded test "Hello, World." In this column, I'm going to do something more useful: retrieve a collection of Customer objects from the server and display them in a dropdown list.
On the Server
I'll get the server-side code (and the client-side code that hasn't changed from my previous column) out of the way first.
My Customer Controller, as shown in Listing 1, currently has two action methods. One method returns the initial Customer View while the other returns my list of Customer objects. While I could have used ASP.NET Web API for the second method, I'd prefer to keep all the code for my Customers in a single Controller.
Listing 1: Server-Side Customers Controller
namespace CustomerOrders.Controllers
{
public class CustomerController: Controller
{
public ActionResult Customers()
{
return View("Customers");
}
public ActionResult GetCustomers()
{
CustomerOrdersContext db;
db = new CustomerOrdersContext();
return Json(db.Customers.ToList(),JsonRequestBehavior.AllowGet);
}
Listing 2 shows my View, which hasn't changed much from my previous column. All the View has to do is configure my environment for Angular2 and load the initial file that contains the entry point for my application (./scripts/app/main). The only thing that has changed since my last column is that I've replaced my customer "Hello, World" tag with a new custom tag (Customer-app). I'll use the Customer-app element to define the part of the page that's controlled by Angular2.
Listing 2. A View to Initialize and Load an Angular2 Application
<html>
<head>
<title>Customer Information</title>
<base href="/">
<script src="~/Scripts/systemjs.config.js"></script>
<script>
System.config({
defaultJSExtensions: true
});
System.import('./scripts/app/main')
.catch(function (err) { console.error(err); });
</script>
</head>
<body>
<Customer-app/>
</body>
</html>
While it's not strictly part of my server-side code, here's that entry point code, which I have in a file called main.ts in my project's Scripts/App folder (this is unchanged from my previous column):
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
That last line expects to load a component called AppModule from a file called app.module.ts in the same folder as my main.ts file. That AppModule file, the 'root file' for my application has changed quite a bit from my previous, "Hello, World" application.
Defining the Application's Resources
Essentially, an AppModule component specifies all the resources that your application will require. Since I'm coding in TypeScript, I need the declarations that define those resources which I reference using import statements.
I begin by referencing the Angular2 modules that my application will need. The new addition here, compared to my previous column, is HttpModule which lets me issue Ajax calls to my server-side GetCustomers method:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
Next, I reference the modules that I create as part of the application. The first module is AppComponent (in a file called app.component.ts) that starts my application running. The next module referenced, called CustomerList (in a file called CustomerList.component.ts), manages the first page displayed:
import { AppComponent } from './app.component';
import { CustomerListComponent } from './CustomerList.component';
Still in my app.module.ts file, I define the AppModule startup class itself. I don't actually have any code to put in the class itself -- the class's primary reason for existing is so that I can decorate it with NgModule, which is where I specify the resources my application needs.
In the NgModule decorator I specify the Angular2 modules I'll be using in the imports property (BrowserModule is required for any Angular2 app and I've also included HttpModule to support making Ajax calls).
In the declarations property, I specify the classes I'll use, which currently correspond to my custom elements (Appcomponent is the traditional start point for an Angular2 application and CustomerListComponent will handle displaying my dropdown list of Customers). I also register any providers that I'll be using in the providers property (in this case, that's I'm creating a class called CustomerServerProvider that handles my server-side communication).
Finally, I define any components that should be loaded when AppModule is loaded, in the bootstrap property (in this case, that's AppComponent, the start point for my application).
I also export the class so that it's visible to the code in main.ts:
@NgModule({
imports: [ BrowserModule, HttpModule ],
declarations: [ AppComponent, CustomerListComponent ],
providers: [CustomerServiceProvider],
bootstrap: [AppComponent]
})
export class AppModule { }
Starting the Application
The last step in defining my application is writing my AppComponent class (I put it in a file called app.component.ts). This class, bootstrapped from AppModule, specifies the initial actions to be performed.
Again, I don't do anything in the class itself -- it's just there so I can apply the Component decorator to it (and using Component requires that I import its definition from angular/core). In the Component decorator, I specify in the selector property that Angular2 should search for my Customer-app element and replace it with the HTML in I've defined in the template property:
import { Component } from '@angular/core';
@Component({
selector: 'Customer-app',
template: `<CustomerList></CustomerList>`,
})
export class AppComponent { }
I export this class so that it's visible to the code in App.module.ts.
As my application becomes more complex, I'll might well end up adding more elements to my template and defining more components to process those elements. Right now, however, all I want to do is replace CustomerList with a dropdown list displaying my Customers. I'll do that in a component whose name matches the element: CustomerList.
Displaying a Select List
In my CustomerList class, I begin by importing definitions of the modules that I'll be using. First, I import Angular2's Component and OnInit modules (I'll need OnInit to trigger initial processing when CustomerList is loaded by AppComponent):
import { Component, OnInit } from '@angular/core';
Next I import my Customer module (in a file called Customer.data.ts) that holds the client-side definition of my Customer object. I also import the definition of my CustomerServiceProvide (in a file called Customer.service.ts) so that I can use it in this class:
import { Customer } from './Customer.data'
import { CustomerServiceProvider } from './Customer.services'
Here's the definition of the Customer class that I'll be using to work with the JSON objects returned from my Action method:
export class Customer {
public Id: number;
public LastName: string;
public FirstName: string;
public CustCreditStatus: string;
public CreditLimit: string;
}
Like my AppComponent class, I apply the Component decorator to specify how I want this CustomerList class to be used. In the selector property, I specify that this class is to be used with CustomerList element (which you saw added in AppComponent).
The template property in this component contains more interesting HTML because I want to generate a select element with one option element for each Customer object I retrieve from the service. To generate those multiple option elements, I use Angular2's *ngFor, telling it to loop through a collection called customers. For each object in that collection, I use the objects' Id, FirstName, and LastName properties to construct my option elements. I use double French braces ( {{ }} ) to mark properties whose value is to be inserted into the page. Finally, in the providers property, I specify any providers that I'll be using in this component (in this case, that's my CustomerServiceProvider):
@Component({
selector: 'CustomerList',
template: `<h1>Customer List</h1>
<select>
<option *ngFor="let cust of customers" value="{{cust.Id}}">{{cust.FirstName}} {{cust.LastName}}</option>
</select>`,
providers: [ CustomerServiceProvider ]
})
In the CustomerList class, I have some actual code. First, I have the class extend Angular2's OnInit class. This adds the ngOnInit method to the class which will be run when CustomerList is invoked (i.e. whenever CustomerList element is found). I also define (as a field in the class) the customers collection that ngFor uses, specifying that it's an array of Customer objects. In my class' constructor, I create another field called CustomerService that will hold my CustomerServiceProvider. I don't have to actually load my provider -- Angular2 will take care of that for me through its dependency injection engine.
Here's what the start of my CustomerListComponent class looks like:
export class CustomerListComponent implements OnInit {
customers: Customer[];
constructor(private CustomerService: CustomerServiceProvider) {}
In the ngOnInit is called by Angular2, all I want to do is load my customers field. I do that through a method on my CusomterServiceProvider, which I access through the CustomerService field I set up in my constructor (I cleverly called this method "getCustomers").
My getCustomers method returns a collection of Observable objects using RxJs (which I've discussed elsewhere). With RxJs, what actually triggers retrieving the objects is subscribing to that collection, which I do here. The subscribe method accepts an arrow expression and passes to that expression whatever objects the method retrieves. I use that arrow expression to move that collection of Customer object into the field where ngFor expects to find it:
ngOnInit() {
this.CustomerService.getCustomers().subscribe(custs => this.customers = custs);
}
}
Retrieving JSON Objects
Finally, I need to create my service provider that will call my server-side action method. I've tried to make this as self-contained as possible so that I can rewrite it if I change the source of my Customer objects.
Again, I begin by importing the definitions for any modules I use in the class. In this case, that's Observable from the rxjs library (rxjs is added to your project as part of configuring it for Angular2 -- see my previous column). I also need to import any RxJS methods I need (map, in this case). Because I'm going to have Angular2's dependency injection engine take care of instantiating this class, I also need to import Injectable from angular/core. Finally, I need my Customer class to define the objects this provider works with:
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import { Injectable } from '@angular/core';
import { Customer } from './Customer.data'
To make my class injectable through Angular2's dependency injection engine, I decorate my class with Injectable. I also define a field to hold the URL for my Action method to make it easier to change should my service change its location. In the class' constructor, I accept a reference to Angular2's Http module (which, itself, will be loaded by Angular's dependency injection engine):
@Injectable()
export class CustomerServiceProvider {
private url: string = "./Customer/GetCustomers";
constructor(private http: Http) { }
Finally, I write the method that will call my Action method and return an Observable collection of my Customer objects. To call the method, I just use the get method on Angular2's Http component which, when passed the URL to call, returns an Observable collection of Angular2 Response objects:
getCustomers(): Observable<Customer[]> {
var resps: Observable<Response>;
resps = this.http.get(this.url);
With ASP.NET MVC, to convert that collection of Response objects to an Observable array of Customer objects is relatively easy: I just use the RxJs map function. With the map function, I first specify that each object in the input collection is a Response object and that I want the output collection to be an Observable array of Customer objects. I then pass the map method an arrow expression specifying what I want from each Response object. To get the JSON object inside each Response object, I just call the json method on the Response object.
Finally, I return the result:
var custs: Observable<Customer[]>
custs = resps.map<Response, Customer[]>(resp => resp.json());
return custs
}
That's a lot of code though. In defense of Angular2 and TypeScript, I should point out that my getCustomers method could be reduced to a single line of code:
return this.http.get(this.url).map((resp: Response) => resp.json());
In addition, every incremental piece of functionality I add to this application won't require much in the way of additional code. You'll see that in my next column where I'll handle the user selecting a customer from the list and displaying the customer's associated data.