Tuesday, 29 November 2016

Crud Applications In MVC with Angular Js

Introduction

As you know, AngularJS is the most popular JavaScript framework to develop single page applications. You can also use Angular for Insert, Update, Delete and Retrieve operations. This tip will demonstrate how to use Angular with MVC5 and WebAPI2 for CRUD (Create, Read, Update, Delete) operations. Many experienced developers will find this tip very basic, but since the tip is written from the perspective of beginners, I've tried to keep things simple.

Background

In my previous article, I demonstrated CRUD operations with KnockoutJS. Here, I will be using the same database, design and contents, but instead of Knockout, I will be using AngularJS and operations will be handled in WebAPI.

Using the Code

Let's begin !!!

Create 'TblProductList' table with this schema.

Create a new project in ASP.NET MVC 5 and name it as you prefer and select empty project template.
Tick MVC and Web API under Add folders and core references for:

Install Entity Framework 6, Jquery and AngularJS in your project using NuGet Package Manager.
You can also download jquery.js and angular.js from their official website and paste it in 'Scripts' folder of your project.

Right click on Model folder and add a new ADO.NET Entity Data Model. Name it as 'ProductDataContext.edmx'.

Choose 'Generate from Database' and configure the connection string as per your SQL server.

After generating the model, you will get the entity of TblProductList.

Create new folder 'Interface' in root directory. Add a new class 'IProductRepository.cs'.

interface IProductRepository
  {
      IEnumerable<TblProductList> GetAll();
      TblProductList Get(int id);
      TblProductList Add(TblProductList item);
      bool Update(TblProductList item);
      bool Delete(int id);
  }

Create new folder 'Repositories' in root directory. Add a new class 'ProductRepository.cs'. Implement the methods to Create, Read, Update, Delete using Entity Framework.

public class ProductRepository : IProductRepository
   {
       ProductDBEntities ProductDB = new ProductDBEntities();

       public IEnumerable<TblProductList> GetAll()
       {
           // TO DO : Code to get the list of all the records in database
           return ProductDB.TblProductLists;
       }

       public TblProductList Get(int id)
       {
           // TO DO : Code to find a record in database
           return ProductDB.TblProductLists.Find(id);
       }

       public TblProductList Add(TblProductList item)
       {
           if (item == null)
           {
               throw new ArgumentNullException("item");
           }

           // TO DO : Code to save record into database
           ProductDB.TblProductLists.Add(item);
           ProductDB.SaveChanges();
           return item;
       }

       public bool Update(TblProductList item)
       {
           if (item == null)
           {
               throw new ArgumentNullException("item");
           }

           // TO DO : Code to update record into database
           var products = ProductDB.TblProductLists.Single(a => a.Id == item.Id);
           products.Name = item.Name;
           products.Category = item.Category;
           products.Price = item.Price;
           ProductDB.SaveChanges();

           return true;
       }

       public bool Delete(int id)
       {
           // TO DO : Code to remove the records from database
           TblProductList products = ProductDB.TblProductLists.Find(id);
           ProductDB.TblProductLists.Remove(products);
           ProductDB.SaveChanges();
           return true;
       }
   }

Right click on Controllers folder and add new WebAPI 2 Empty Controller 'ProductController.cs':

public class ProductController : ApiController
   {
       static readonly IProductRepository repository = new ProductRepository();

       public IEnumerable GetAllProducts()
       {
           return repository.GetAll();
       }

       public TblProductList PostProduct(TblProductList item)
       {
           return repository.Add(item);
       }

       public IEnumerable PutProduct(int id, TblProductList product)
       {
           product.Id = id;
           if (repository.Update(product))
           {
               return repository.GetAll();
           }
           else
           {
               return null;
           }
       }

       public bool DeleteProduct(int id)
       {
           if (repository.Delete(id))
           {
               return true;
           }
           else
           {
               return false;
           }
       }
   }

Right click on Controllers folder and add new Controller 'HomeController.cs':

public class HomeController : Controller
   {
       public ActionResult Product()
       {
           return View();
       }
   }

Right click on ActionResult Product() and add a view 'Product.cshtml'.

@{
    ViewBag.Title = "Products List";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
@section scripts {

    <link href="~/Content/CustomStyle.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/AngularDemo.js"></script>
}
<div ng-app="demoModule" id="body">
    <div ng-controller="demoCtrl">
        <h2>AngularJS CRUD Operations with MVC5 WebAPI</h2>

        <h3>List of Products</h3>

        <table ng-cloak>
            <thead>
                <tr>
                    <th style="display: none;">ID</th>
                    <th>Name</th>
                    <th>Category</th>
                    <th>Price</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="items in productsData">
                    <td hidden="hidden">{{items.Id}}</td>
                    <td>{{items.Name}}</td>
                    <td>{{items.Category}}</td>
                    <td>{{items.Price | currency:'&#8377;':2}}</td>
                    <td>
                   <button ng-model="$scope.Product" 
                   ng-click="edit(productsData[$index])">Edit</button>
                   <button ng-click="delete($index)">Delete</button>
                    </td>
                </tr>
            </tbody>
            <tfoot>
                <tr>
                    <td colspan="6">
                        <hr />
                    </td>
                </tr>
                <tr>
                    <td>Total :</td>
                    <td></td>
                    <td><label ng-bind="total() | 
                    currency:'&#8377;':2"></label></td>
                    <td></td>
                </tr>
            </tfoot>
        </table>
        <br />
        <div style="border-top: solid 2px #282828; width: 430px; height: 10px"> </div>

        <div ng-show="Product.Id != '' ">
            <div>
                <h2>Update Product</h2>
            </div>
            <div hidden="hidden">
                <label for="id">Id</label>
                <input type="text" data-ng-model="Product.Id" />
            </div>
            <div>
                <label for="name">Name</label>
                <input type="text" data-ng-model="Product.Name" />
            </div>

            <div>
                <label for="category">Category</label>
                <input type="text" data-ng-model="Product.Category" />
            </div>

            <div>
                <label for="price">Price</label>
                <input type="text" data-ng-model="Product.Price" />
            </div>
            <br />
            <div>
                <button data-ng-click="update()">Update</button>
                <button data-ng-click="cancel()">Cancel</button>
            </div>
        </div>

        <div ng-hide="Product.Id != '' ">
            <div>
                <h2>Add New Product</h2>
            </div>
            <div>
                <label for="name">Name</label>
                <input type="text" data-ng-model="Product.Name" />
            </div>

            <div>
                <label for="category">Category</label>
                <input type="text" data-ng-model="Product.Category" />
            </div>

            <div>
                <label for="price">Price</label>
                <input type="text" data-ng-model="Product.Price" />
            </div>
            <br />
            <div>
                <button data-ng-click="save()">Save</button>
                <button data-ng-click="clear()">Clear</button>
            </div>
        </div>
    </div>
</div>

Create a new JavaScript file 'AngularDemo.js' in Scripts folder to implement CRUD operations using Angular code.

// Defining angularjs module
var app = angular.module('demoModule', []);

// Defining angularjs Controller and injecting ProductsService
app.controller('demoCtrl', function ($scope, $http, ProductsService) {

    $scope.productsData = null;
    // Fetching records from the factory created at the bottom of the script file
    ProductsService.GetAllRecords().then(function (d) {
        $scope.productsData = d.data; // Success
    }, function () {
        alert('Error Occured !!!'); // Failed
    });

    // Calculate Total of Price After Initialization
    $scope.total = function () {
        var total = 0;
        angular.forEach($scope.productsData, function (item) {
            total += item.Price;
        })
        return total;
    }

    $scope.Product = {
        Id: '',
        Name: '',
        Price: '',
        Category: ''
    };

    // Reset product details
    $scope.clear = function () {
        $scope.Product.Id = '';
        $scope.Product.Name = '';
        $scope.Product.Price = '';
        $scope.Product.Category = '';
    }

    //Add New Item
    $scope.save = function () {
        if ($scope.Product.Name != "" &&
       $scope.Product.Price != "" && $scope.Product.Category != "") {
            // Call Http request using $.ajax

            //$.ajax({
            //    type: 'POST',
            //    contentType: 'application/json; charset=utf-8',
            //    data: JSON.stringify($scope.Product),
            //    url: 'api/Product/PostProduct',
            //    success: function (data, status) {
            //        $scope.$apply(function () {
            //            $scope.productsData.push(data);
            //            alert("Product Added Successfully !!!");
            //            $scope.clear();
            //        });
            //    },
            //    error: function (status) { }
            //});

            // or you can call Http request using $http
            $http({
                method: 'POST',
                url: 'api/Product/PostProduct/',
                data: $scope.Product
            }).then(function successCallback(response) {
                // this callback will be called asynchronously
                // when the response is available
                $scope.productsData.push(response.data);
                $scope.clear();
                alert("Product Added Successfully !!!");
            }, function errorCallback(response) {
                // called asynchronously if an error occurs
                // or server returns response with an error status.
                alert("Error : " + response.data.ExceptionMessage);
            });
        }
        else {
            alert('Please Enter All the Values !!');
        }
    };

    // Edit product details
    $scope.edit = function (data) {
        $scope.Product = { Id: data.Id, Name: data.Name, Price: data.Price, Category: data.Category };
    }

    // Cancel product details
    $scope.cancel = function () {
        $scope.clear();
    }

    // Update product details
    $scope.update = function () {
        if ($scope.Product.Name != "" &&
       $scope.Product.Price != "" && $scope.Product.Category != "") {
            $http({
                method: 'PUT',
                url: 'api/Product/PutProduct/' + $scope.Product.Id,
                data: $scope.Product
            }).then(function successCallback(response) {
                $scope.productsData = response.data;
                $scope.clear();
                alert("Product Updated Successfully !!!");
            }, function errorCallback(response) {
                alert("Error : " + response.data.ExceptionMessage);
            });
        }
        else {
            alert('Please Enter All the Values !!');
        }
    };

    // Delete product details
    $scope.delete = function (index) {
        $http({
            method: 'DELETE',
            url: 'api/Product/DeleteProduct/' + $scope.productsData[index].Id,
        }).then(function successCallback(response) {
            $scope.productsData.splice(index, 1);
            alert("Product Deleted Successfully !!!");
        }, function errorCallback(response) {
            alert("Error : " + response.data.ExceptionMessage);
        });
    };

});

// Here I have created a factory which is a popular way to create and configure services.
// You may also create the factories in another script file which is best practice.

app.factory('ProductsService', function ($http) {
    var fac = {};
    fac.GetAllRecords = function () {
        return $http.get('api/Product/GetAllProducts');
    }
    return fac;
});

Note

You can use $.ajax from jQuery script or $http from angular.js script to make HTTP request.
It's better to use $http because using $.ajax is also forcing us to use $scope.apply which is not needed if you use $http.

You can also use $resource which is considered to be best practice for doing CRUD operations in RESTful Web API. This tutorial is for beginners, so I tried to keep things simple by using $http.

As per the architecture perspective, instead of controller, you can call $http request for POST, PUT, DELETE in our custom factory like I have done for $http.get(), so that our controller will look clean and exhibits proper Separation of Concern.

Now, add a Layout view '_Layout.cshtml'.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <meta name="viewport" content="width=device-width" />
</head>
<body>
    <div id="body">
        @RenderSection("featured", required: false)
        <section class="content-wrapper main-content clear-fix">
            @RenderBody()
        </section>
    </div>

    @RenderSection("scripts", required: false)
</body>
</html>

Add stylesheet 'CustomStyle.css' to improve the look and feel of the page.

[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
    display: none !important;
}

body {
            margin: 20px;
            font-family: "Arial", "Helventica", sans-serif;
        }

        label {
            width: 80px;
            display: inline-block;
        }

        button {
            display: inline-block;
            outline: none;
            cursor: pointer;
            text-align: center;
            text-decoration: none;
            padding: .4em 1.1em .4em;
            color: #fef4e9;
            border: solid 1px #006fb9;
            background: #1276bb;
        }

            button:hover {
                text-decoration: none;
                background: #282828;
                border: solid 1px #000;
            }

        table {
            padding-top: 1em;
        }

        thead, tfoot {
            font-weight: 600;
        }

        th, td {
            padding: .1em .5em;
            text-align: left;
        }

            td li, td ul {
                margin: 0;
                padding: 0;
            }

            td li {
                display: inline;
            }

                td li::after {
                    content: ',';
                }

                td li:last-child::after {
                    content: '';
                }

Note

Here, I have defined style for ng-cloak directive used in our 'Product.cshtml'.
According to AngularJS Documentation.

The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.

The reason for adding the class is because the ng-cloak directive is parsed after the html has been displayed, so there is always the possibility that your JS thread dies and still displays anything like {{something here}}

Now, change the default controller and action in Route.Config.cs.

routes.MapRoute(
              name: "Default",
              url: "{controller}/{action}/{id}",
              defaults: new { controller = "Product", action = "Product", id = UrlParameter.Optional }
           );

And also change the default routeTemplate to add action in WebApiConfig.cs.

config.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{action}/{id}",
               defaults: new { id = RouteParameter.Optional }
           );

Hit Ctrl+F5.

That's it !!!


Congratulations!!! Now, you have successfully implemented CRUD operations in ASP.NET MVC 5 using WebAPI 2 using AngularJS.

Please comment for any queries.

Happy coding! :)

No comments:

Post a Comment