In my last two articles in CODE Magazine, you learned to use AngularJS, which is a client-side framework, to replace code that you used to write all on the server-side using Web Forms or MVC. Angular is becoming the framework of choice to develop modern Web applications due to its ease-of-use and great industry support. In this article, you're going to learn to use Angular to insert, update, and delete data. You're going to build some Web API calls and connect to those APIs using the Angular data server. Using Angular data binding, the data from your user will be automatically updated in client-side objects, which are then sent to the server via Web API calls.
Related Articles
Get a Single Product
In the last article (CODE Magazine July/August 2016), you retrieved a collection of product objects from the server and stored them into an array in your Angular module's scope. You can either retrieve a single product from that array when the user clicks on the Edit button, or you can re-retrieve the data from the server. I'm going to show you how to use the Web API to retrieve the product from the server. Not because I have to, but just for completeness of the Web API.
Additionally, there will be times when you want to make sure that you always have the latest data when entering Edit mode. The first step is to add one more variable to your module scope; a single product object. Declare the variable as an empty literal object, as shown in the following line of code.
vm.product = {};
Open up the ProductController.cs
file that you built in the last article and locate the Get()
method with a single integer value passed in as a parameter. Change this method's signature (shown in Listing 1) to use the IHttpActionResult
return value like the Web API you wrote in the last article. For the purposes of this article, I'm not using a real data store; instead I'm using some mock data. Feel free to replace this code with your data layer of choice, and retrieve real data. Use the Ok()
or NotFound()
methods of the ApiController
base class to create an HttpResponseMessage
. This message returns a 404 HTTP status code or a 200 depending on whether or not the single product object is found.
Listing 1: Build a Web API method to return a single product object
[HttpGet()]
public IHttpActionResult Get(int id) {
IHttpActionResult ret;
List<Product> list = new List<Product>();
Product prod = new Product();
list = CreateMockData();
prod = list.Find(p => p.ProductId == id);
if (prod == null) {
ret = NotFound();
}
else {
ret = Ok(prod);
}
return ret;
}
Now that you have the Web API written, open up your productController.js
file and add a get()
function that accepts a single parameter. This parameter, named id
, is set from the ProductId
property that was used in the Edit button when building the HTML product table. After successfully retrieving the product object from the Web API call, assign that data to the vm.product
object you declared earlier. This object is going to be bound to input fields, as shown in Listing 2.
Listing 2: Get a single product object Web API using the Angular data service
function get(id) {
// Call Web API to get a product
dataService.get("/api/Product/" + id)
.then(function (result) {
// Display product
vm.product = result.data;
// Fix date field to local format
vm.product.IntroductionDate = new
Date(vm.product.IntroductionDate).toLocaleDateString();
setUIState(pageMode.EDIT);
}, function (error) {
handleException(error);
});
}
When you build the HTML table, the editClick()
function is called from the Edit
anchor tag, as shown in the next code snippet.
<tr ng-repeat="product in products">
<td class='pdsa-action-button-column'>
<a href='#' class='btn btn-default btn-sm'
ng-click="editClick(product.ProductId)">
<i class='glyphicon glyphicon-edit'></i>
</a>
</td>
Modify the editClick
event to call your newly written get()
function and pass in the ProductId
value. Remove the call to the setUIState()
function, as this method is called only if the retrieval of the product data from the server is successful.
function editClick(id) {
get(id);
}
In Listing 3, you can see that each input field has an attribute added to it called ng-model. This is an Angular directive that you use to specify the name of the object on the scope you wish to bind to for input. By default, the ng-model
directive uses two-way data binding. This means that when you modify the properties listed in the value portion of the attribute, the data on the screen is updated, and vice versa.
Listing 3: Bind all properties in the Product object using the ng-model directive
<div class="form-group">
<label for="ProductName">Product Name</label>
<input class="form-control"
id="ProductName"
ng-model="product.ProductName"
type="text" />
</div>
<div class="form-group">
<label for="IntroductionDate">Introduction Date</label>
<input class="form-control"
id="IntroductionDate"
ng-model="product.IntroductionDate"
type="text" />
</div>
<div class="form-group">
<label for="Url">Url</label>
<input class="form-control"
id="Url"
ng-model="product.Url"
type="text" />
</div>
<div class="form-group">
<label for="Price">Price</label>
<input class="form-control"
id="Price"
ng-model="product.Price"
type="text" />
</div>
Date Handling
In the get()
function, you might have noticed that I wrote code to take the IntroductionDate
property, convert it to a JavaScript date using the Date()
function, then applied the .toLocaleDateString()
to put it into a format that a user would like to see. The reason you have to do this is because JSON serializes dates in an ISO 8601 format. This means that dates are transmitted as a Universal Time Coordinate (UTC) format and may look like one of the following.
2016-11-05T00:00:00
2016-11-05T00:00:00Z
The .toLocaleDateString()
function uses the current browser's date format to put the UTC formatted date into a format that a user can understand easier, such as 11-05-2016 or 11/05/2016, or maybe 05.11.2016. The format chosen will be determined by what the user has configured in their browser.
Date Problem with Internet Explorer
In all browsers except Internet Explorer (IE), converting dates to the local format using the .toLocalDateString()
function poses no problems. IE adds some additional characters in order to display dates in either Left-To-Right or Right-To-Left format. These extra characters cause problems when you're trying to take the bound data from Angular and have it serialize that data as JSON to send to your Web API. The JavaScript Date()
constructor takes many different date formats and attempts to convert to a UTC date for sending to the server. However, because these extra characters are in the string, they cause problems with the Date conversion.
In the validate()
function, which is called from both the insertData()
and the updateData()
functions, check all your date inputs and apply the code shown in the next snippet to replace the hidden characters with empty strings. I'm not checking for a valid date in the validate()
function; that's something you should do prior to passing in the resulting string to the Date()
function's constructor. Finally, apply the .toISOString
function to turn your new date into a UTC date, which is then ready to send to the server.
function validate() {
var ret = true;
if (vm.product.IntroductionDate != null) {
// Fix up date - NOTE: Assumes a valid date
vm.product.IntroductionDate =
new Date(vm.product.IntroductionDate
.replace(/\u200E/g, ''))
.toISOString();
}
return ret;
}
Update a Product
Create a Put()
method in your Web API to which you'll send the updated input. Open the ProductController.cs
file and write the Put()
method, as shown in Listing 4. There are two additional methods called by the Put()
method: Exists()
and Update()
. Exists()
checks to see whether the product you are trying to update exists in your data store. Update()
modifies the data in your data store using your data layer. I'm not going to show these methods here, as that code differs depending on the data layer you are using. If the Exists()
method returns a false
, you pass back a NotFound
status code to the front-end. If the update is successful, pass back an OK status; otherwise it returns an InternalServerError
.
Listing 4: Add a Put method to your Web API to update data
[HttpPut()]
public IHttpActionResult Put(int id, Product product) {
IHttpActionResult ret = null;
if (Exists(id)) {
if (Update(product)) {
ret = Ok(product);
}
else {
ret = InternalServerError();
}
}
else {
ret = NotFound();
}
return ret;
}
Modify the function updateData()
in your productController.js
file. After calling the validate()
function to perform any client-side validation checks and fixing up any date values, call the put()
function on the Angular data service, as shown in Listing 5. If the call is successful, you update the product
object from the data returned. Locate the product
object within the Products
array using the Map
function. Using the index returned from the Map
function, replace the updated product
object into the Products
array. Once the Products
array receives this new data, the HTML table that's bound to this array will be updated and display the new data.
Listing 5: Call the Put method from Angular and update the returned product
function updateData() {
// Check data
if (validate()) {
dataService.put("/api/Product/" +
vm.product.ProductId, vm.product)
.then(function (result) {
// Update product object
vm.product = result.data;
// Get index of this product
var index = vm.products.map(function (p)
{ return p.ProductId; }).indexOf(vm.product.ProductId);
// Update product in array
vm.products[index] = vm.product;
setUIState(pageMode.LIST);
}, function (error) {
handleException(error);
});
}
}
Insert a Product
You're now ready to insert a new product. Open your ProductController.cs
file and locate the Post()
method. Replace the default template with the code shown in Listing 6. You need to create your own Add()
method in order to store the product data into your data store. Because I'm using mock data, I'm just returning a true
so processing can continue. If the adding of the data is successful, use the Created<T>()
method as the return result. This method returns a status code of 201 to the front-end. This is still considered a success code and won't generate an error. The Created<T>()
method returns not only the entity that was added back to the frontend, it also returns the URI that the frontend developer can use to re-query the data just added. This might look like /api/Product/34.
Listing 6: A Post method in your Web API should return a Created() response
[HttpPost()]
public IHttpActionResult Post(Product product) {
IHttpActionResult ret = null;
if (Add(product)) {
ret = Created<Product>(Request.RequestUri +
product.ProductId.ToString(), product);
}
else {
ret = InternalServerError();
}
return ret;
}
Now that you have the Post()
method created, call it from a function named insertData()
in your productController.js
file. Modify the insertData()
function with the code shown in Listing 7. In this code, you use the post()
function of the Angular data service to call your new Post()
Web API. If the call is successful, a 201 is returned.
Listing 7: Call the Post method from Angular and push data onto the array
function insertData() {
// Check data
if (validate()) {
dataService.post("/api/Product", vm.product)
.then(function (result) {
// Update product object
vm.product = result.data;
// Add Product to Array
vm.products.push(vm.product);
setUIState(pageMode.LIST);
}, function (error) {
handleException(error);
});
}
}
Get the new product data from the result.data
property and set the vm.product
object. Push the new object onto your vm.products
array, which, via data binding, updates the HTML table.
Delete a Product
To delete a product, add a Delete()
method in your ProductController.cs
. This method is very similar to the Put()
method you created earlier. You check for the existence of the product you wish to delete. If it exists, you attempt to delete a product. Depending on whether or not it's deleted determines which return value you send back to the calling program.
The Delete()
method is called from a method called deleteData()
in your productController.js
file. This function is called from the deleteClick()
function, which is called from the Delete button on your HTML table, as shown in the next code snippet.
<td class='pdsa-action-button-column'>
<a href='#' class='btn btn-default btn-sm'
ng-click="deleteClick(product.ProductId)">
<i class='glyphicon glyphicon-trash'></i>
</a>
</td>
Listing 8: Add a Web API method to delete data
[HttpDelete()]
public IHttpActionResult Delete(int id) {
IHttpActionResult ret = null;
if (Exists(id) {
if (DeleteProduct(id)) {
ret = Ok(true);
}
else {
ret = InternalServerError();
}
}
else {
ret = NotFound();
}
return ret;
}
In your productContoller.js
file, locate the deleteClick()
function and replace it with the code shown below. In this code, confirm that the user does want to delete this product. If they do, call the deleteData()
function shown in Listing 9.
function deleteClick(id) {
if (confirm('Delete this product?')) {
deleteData(id);
}
}
Listing 9: Call the Post method from Angular and return a Created() response
function deleteData(id) {
if (confirm("Delete this Product?")) {
dataService.delete("/api/Product/" + id)
.then(function (result) {
// Get index of this product
var index = vm.products.map(function (p)
{ return p.ProductId; }).indexOf(id);
// Remove product from array
vm.products.splice(index, 1);
setUIState(pageMode.LIST);
}, function (error) {
handleException(error);
});
}
}
The deleteData()
function uses the Angular data service to call the delete
API. If the delete is successful, locate the product ID in the vm.products
array using the map()
function. Using the index of the location of the product
object in the array, call the splice()
function on the array to remove that item. Once removed, this updates the HTML table automatically because of Angular data binding.
Summary
In this article, you continued to learn to use Angular to replace server-side code. You learned how to retrieve a single product using the Web API, and you also learned about date handling and some of the issues you might hit when using Internet Explorer. You then saw how to update, insert, and delete products using Angular and the Web API. In the next article, you'll look at how to handle validation of data.