The Practical Client
Your First Angular App: Events and Updating
Peter completes his walk-through of creating a simple Angular app with TypeScript by responding to events, accepting data from the user and updating data back at the server with an AJAX call.
In an earlier column, I created a simple Angular app in TypeScript that displayed a dropdown list of Customer objects retrieved from an ASP.NET MVC Controller’s Action method. In this column, I’m going to extend that application by letting the user select a Customer from that dropdown list, display that Customer’s information, accept changes to the data from the user and then send those changes back to the server for processing.
Responding to Events
My first step is to extend the template in my CustomerList.Component.ts file to respond to the onchange event in my dropdown list so I can retrieve the Customer the user has selected. In Angular 2, parentheses are used to capture events and associate the event with a method in the component’s class, so I wrap the click keyword in parentheses and then assign a method to the event using the equals sign.
Listing 1 shows my enhanced template, which catches the onchange event in my dropdown list and associates it with a method called CustomerSelected. By passing $event.target.value as a parameter to my method, I pass the current value of the object that fired the event to that method. I’d configured my dropdown list so the value of the currently selected item is the Customer’s Id property, so that’s the value that will be passed to my CustomerSelected method (I’ve also added a new default option to my dropdown list to force the user to change the currently selected item even when picking the first customer in the list). One note about the code: The punctuation mark at the start of the template is a tic, not a single quote.
Listing 1. Catching a Dropdown List’s onchange Event
@Component({
selector: 'CustomerList',
template: '<h1>Customer List</h1>
<select (change)="CustomerSelected($event.target.value)">
<option disabled value="-1"> Select a Customer </option>
...rest of template omitted...,
providers: [ CustomerServiceProvider ]
})
export class CustomerListComponent implements OnInit {
customers: Customer[];
scust: Customer;
CustomerSelected(custId: number) {
this.scust = this.customers.filter(cust => cust.Id == custId)[0];
}
In my previous column, I loaded the customers array (declared at the class level so I can bind to it in my template) with Customer objects retrieved from the server. In my CustomerSelected method, I accept the Customer Id and use that to find all the matching Customers in the customers array. Of course, there should be only one matching Customer, so I pull out the first item in the array returned by the find method and put the Customer object in another variable declared at the class level (called scust).
Two-Way Databinding
My next step is to display the customer information in some textboxes in my template as I did in my previous column. However, this time I also want to accept input from the user, so I need to implement two-way databinding. In Angular 2, you can do that with the ngModel directive, which allows you to bind HTML elements to properties on variables declared at the class level of your component.
However, before using ngModel, I first need to import the Angular FormsModule, which contains it. Doing that sends me back to my app.module.ts file where I add this line:
import { FormsModule } from '@angular/forms';
I also need to extend my NgModule directive with the FormsModule. Still in app.module.ts, I add FormsModule at the end of the two modules I’m already importing:
@NgModule({
imports: [
BrowserModule,
HttpModule,
FormsModule
],
...rest of module omitted...
With FormsModule imported, I can extend my template in my Customerlist.component.ts file using ngModel. I begin by adding input elements and tie them to the properties of whatever Customer object I have in my scust variable. I’ll also add a button to the form for the user to click to trigger the update process.
Following are the additions to my template, showing just one input element -- the one data-bound to the scust object’s LastName property using the ngModel directive. I’ve also shown the button whose onclick event calls a method named SaveCustomer:
... earlier part of the template ...
<h2>Customer Information</h2>
<input id="LastName" type="text" [(ngModel)]="scust.LastName"/><br/>
... more elements ...
<input type="button" value="Save Changes" (click)="SaveCustomer()"/>'
The funny syntax around ngModel is a combination of the data-binding flags (the square brackets) and the event flags (the parentheses). You can show you’re hip and with it in the Angular community by referring to that cluster of punctuation marks as the "banana in a box."
With those changes made, when the user enters a value into my LastName textbox, ngModel will automatically update the related property in the scust variable. Because that object is still part of the customers array, the display in my dropdown list also will be automatically updated.
Updating
Now I’m ready to start thinking about sending my updated object back to the server. I first need to add a method to the service provider to handle sending data to the server. I’d previously imported the Angular HttpModule, which includes methods for generating HTTP requests, so I’ll build on that. Listing 2 shows my provider with my new method added.
Listing 2. A Service Provider for Sending Post Requests to the Server
@Injectable()
export class CustomerServiceProvider {
private getUrl: string = "/home/GetCustomers";
private postUrl: string = "/home/PostCustomer";
constructor(private http: Http) { }
updateCustomer(cust: Customer): Observable<Response> {
var res: Observable<Response>;
res = this.http.post(this.postUrl, cust);
return res;
}
As you can see, I’ve chosen to use the post method on the Angular Http object (which is automatically handed to my provider’s constructor thanks to the @Injectable directive). The post method must be passed two parameters: the URL to send the request to (which I’ve stored in a variable for easy updating) and the object to be sent. In the code in Listing 2, I pass the Customer object to be sent to the server to this method.
The Http object’s post method returns an Observable collection of Response objects. I chose to return that collection from my method.
Because my method is returning an Observable collection, to have my method actually executed, I have to subscribe to the collection. I do that back in my CustomerList.Component.ts file in that SaveCustomers method I tied to my button element. In the method, I subscribe to my CustomerService updateCustomer method, passing the updated scust variable. I pass an arrow expression to the subscribe method to specify what I’ll do with the Response objects returned from the method. I’ve chosen to just display the statusText property of the HTTP responses:
SaveCustomer() {
this.CustomerService.updateCustomer(this.scust).subscribe((msg: Response) => alert(msg.statusText));
}
One more thing: I need to add an imports statement for the Response object to this file so that this code knows about the object:
import { Response } from '@angular/http';
Back at the Server
In the ASP.NET MVC Controller that’s called by my post method, I need an Action method that will accept the JSON object sent by my http.post. In my previous column, I created a server-side Customer object in C# with properties that match the properties in my client-side TypeScript code (that is, the properties have identical names and compatible data types) so I can use it as the parameter to my Action method in Listing 3. I wrote that method to handle both updates and inserts. The code first checks to see if the Customer object exists and, if the object doesn’t exist, the code adds it. If the object does exist, the code updates the existing object. Finally, the method just returns a string saying everything worked.
Listing 3. An ASP.NET MVC Action Method to Process Customer Updates
public ActionResult postCustomer(Customer newCust) {
CustomerOrdersContext db;
db = new CustomerOrdersContext();
Customer oldCust;
oldCust = (from c in db.Customers
where c.Id == newCust.Id
select c).FirstOrDefault();
if (oldCust == null) {
db.Customers.Add(newCust);
}
else {
oldCust.LastName = newCust.LastName;
}
db.SaveChanges();
return Content("Changes Saved");
}
Looking Back
Of the frameworks I’ve looked at so far (including Knockout, Breeze, Backbone, and React/JSX), my favorite is probably still Knockout precisely because it does very little. It’s just a great data-binding solution (and, coupled with Breeze, a great AJAX wrapper, also). Backbone is my least favorite of the frameworks I’ve worked with (too much to learn) while Angular is, for me, a close runner-up to Knockout. With the experience I’ve gained working with Angular and React/JSX, I suspect Backbone wouldn’t seem as odd to me now as it did when I first worked with it.
And, as usual with my columns on using TypeScript with some framework, there’s much more in the framework than I’ve discussed. There’s another way to implement databinding, for example (you can also do it at the form level). I’ve pretty much ignored injection management, and I suspect most applications would require the ability to navigate to another view. Normally, I’d do more a few more columns on Angular to cover those features. However, because Angular is written in TypeScript, there are lots of TypeScript resources available for Angular -- there's not really a gap that needs filling.
However, that does leave me looking for another framework to try out. If you have a suggestion, I’m interested.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.