Angularjs CRUD with Web API, Entity Framework & Bootstrap modal popup part 1

Angularjs search Customer List Add/Insert Customer with modal popup Update Customer in angularjs modal popup Delete Customer with web api action View Customer Detail in child view of ui.router View Customer Contacts in second child view of ui.router UI Routing parent child view Directive to bind the list of customers

Search sort Insert update and delete are the basic features we need to learn first to learn any language, In this article we will try to create these features with Web API, bootstrap modal popup and ui.router. We will use enetity framework code first to save and retrieve data from database. We will extend our previous article example to achieve our goal.

Here is the list of features we are going to cover and the final image:

  • Search Customer List
  • Add/Insert Customer
  • Update Customer
  • Delete Customer
  • View Customer Detail
  • View Customer Contacts
  • UI Routing parent child view
  • Directive to bind the list of customers

alt text

Let's analyse how the page will open and work, on page load we will load the left side "Search Customers" section, the parent view, where we can search the customer from database, delete customer and on clicking the link will show the customer detail "first child" without loading the entire page but only refresh the customer detail section which contains another child "contact detail", on clicking the contact detail link it will show all the contacts without refresh the entire page but only the second child. This is the beauty of UI Routing.

Here is the routing configuration for these three parent child view:

.state('customer', {
    url: '/customer',
    templateUrl: 'app/customer/customer.html',
    controller: 'customerCtrl'
})
.state('customer.detail', {
    url: '^/customer/detail/{customerId:[0-9]{1,5}}',
    templateUrl: 'app/customer/customerdetail.html',
    controller: 'customerCtrl'
})
.state('customer.detail.contact', {
    url: '^/customer/detail/contact/{customerId:[0-9]{1,5}}',
    templateUrl: 'app/customer/contact.html',
    controller: 'customerContactCtrl'
}) 
  • First one is simple no need to explain
  • Second: customer.detail
    • ^ is used to start path from root
    • {customerId:[0-9]{1,5}} customer Id parameter which must be a number of 1 to 5 digits
  • Same for customer.detail.contact

First we will see the Web Method "Search" in CustomerController derived from ApiController, don't worry for rest of the code, I will share the link to download the complete code, try to understand the current code only

DataContext db = new DataContext();

[Route("api/Customer/search")]
[HttpGet]
public IEnumerable<Customer> SearchCustomers(string searchText)
{
    searchText = searchText ?? "";
   return db.Customers
            .Where(x => x.FullName.Contains(searchText) ||
                x.Country.Contains(searchText) ||
                x.City.Contains(searchText)
        )
        .Take(20);
}

We are returning only 20 records, if you want to search some specific record by name country or city just type that it will search the record and fill with animation, we will see the HTML latter, let's create the service to call the above method

var app = angular.module('MyApp');
app.factory('Customer', ['$q', '$http', function ($q, $http) {

    var baseUrl = 'api/customer/';
    var contactBaseUrl = 'api/Contact/';

    var customerService = {};
    customerService.customers = [];
    customerService.currentCustomer = {};

    // Search Customers
    customerService.search = function (text) {
       var deferred = $q.defer();
       return $http({
           url: baseUrl + 'search',
           method: 'GET',
           params: { 'searchText': text },
           cache: true
       }).success(function (data) {
           deferred.resolve(
               customerService.customers = data);
       }).error(function (error) {
           deferred.reject(error);
       })
       return deferred.promise;
    }

    return customerService;
}]);
  • On top there are some variables
  • Created a blank variable customerService, in which added search method with a parameter to accept the searching text
  • We are using $q defer method call the service asynchronously
  • Finally returning the promise

Time to write the controller to call this service method, create a JavaScript file customerCtrl

var app = angular.module("MyApp")
app.controller('customerCtrl', ['$scope', '$state', '$stateParams', '$modal', '$log', 'Customer', function ($scope, $state, $stateParams, $modal, $log, Customer) {

    var customerId = $stateParams.customerId;

    $scope.searchText = '';
    $scope.customers = searchCustomers();
    $scope.contacts = [];
    $scope.customer = {};
    $scope.currentCustomer = {};

    function searchCustomers() {
        Customer.search($scope.searchText)
        .then(function (data) {
            $scope.customers = Customer.customers;
        });
    };
}]);
  • We are injecting the 'Customer' service and using as the parameter
  • Calling the Customer.seearch method on our service which call the Web API method
  • Rest are some variable on scope to bind the data in view
  • we use $scope.searchText = '' to keep track of search text on the page

Create an HTML file customer.html and add following code

<div class="row">
    <div class="col-lg-10">
        <h1>Manage Customers</h1>  
    </div>
    <div class="col-lg-2">
        <button ng-click="addCustomer()" class="btn btn-success btn-lg">
            <i class="glyphicon glyphicon-plus"></i> Add Customer
        </button>
    </div>
</div>
<div class="row">
    <div class="col-lg-3" style="border:1px solid #999; overflow-y:hidden">
        <!-- Search customers -->
        <h3>Search Customers</h3>
        <div class="input-group">
            <input type="text"
                   ng-model="searchText"
                   class="form-control"
                   placeholder="Enter search text">
            <span class="input-group-addon" id="basic-addon1">
                <i class="glyphicon glyphicon-search"></i>
            </span>
        </div>

        <div class="col-lg-12 customer-row animate" ng-repeat="customer in customers">
            <as-customer></as-customer>
        </div>
    </div>
    <div class="col-lg-9" style="border:1px solid #999; overflow-y:auto">
        <div ui-view></div>
    </div>
</div>
  • First section have the title of the page and Add Customer button
  • Second section is devided in two parts, left (col-lg-3) and right (col-lg-9) section
  • Left part to keep the list of customer
  • Customer list pan use which is directive to cleaning the code
  • Right pan have a div vi-view to render the child view (customer detail)

    Here is the code for directive, it is not necessary even you can use the HTML directly here, create a JavaScript file say asCustomer.js, and add following code in it, it's and element directive

    var app = angular.module('MyApp'); app.directive("asCustomer", function () { return { restrict: 'E', replace: 'true', templateUrl: 'app/customer/userListRow.html' } });

Create a separate HTML file in customer folder with name userListRow.html and add following code in it

<div class="row">
    <div class="col-lg-3">
        <img ng-src="{{customer.Image ? customer.Image : 'customerimages/default-image.png'}}"
             class="img-circle"
             style="max-width:50px"
             alt='customer image' />
    </div>
    <div class="col-lg-9">
        <div style="font-weight:bold;">
            <a ng-click="customerDetail(customer.CustomerId)" style="cursor:pointer">{{customer.FullName}}</a>
            <span class="pull-right" title="Delete customer">
                <i ng-click="deleteCustomer($event, customer.CustomerId)" class="glyphicon glyphicon-trash"></i>
            </span>
        </div>
        <div>{{customer.Address}} {{customer.Country}} {{customer.ZipCode}}</div>
    </div>
</div>

If you don't want to use the directive then add the above html in customer.html to replace , I used it to show how easily we can create the directive in angularjs.

Now if you will run the code, user list will be filled with customers because in controller we already calling the searchCustomer method and assign the data on scope, $scope.customers = searchCustomers(); This code is not enough to search by typing into the text box because we have not written any event method yet. To make it working we will add a $watch to check the changes in text search and call the search method, it's eady so let's add that

$scope.$watch('searchText', function (newVal, oldVal) {
    if (newVal != oldVal) {
        searchCustomers();
    }
}, true);

This watch method will listen any change in search text and match with the old value if different then it will call the search method again for us. Run the application and check it.

In our directive we used a function to delete the customer record deleteCustomer($event, customer.CustomerId), here is the complete html

<span class="pull-right" title="Delete customer">
    <i ng-click="deleteCustomer($event, customer.CustomerId)" class="glyphicon glyphicon-trash"></i>
</span>

In our controller we need a function deleteCustomer with two parameters, one for event and other for customer Id, on the scope and a service method to call the Web API method so a Web API Action, let's add all together:

// Delete a customer in controller
$scope.deleteCustomer = function ($event, id) {
    var ans = confirm('Are you sure to delete it?');
    if (ans) {
        Customer.delete(id)
        .then(function () {
            var element = $event.currentTarget;
            $(element).closest('div[class^="col-lg-12"]').hide();
        })
    }
};
  • First we are taking the confirmation from user
  • If answer is yes then delete the customer from database
  • After deleting hiding that row rather than reloading the list again

Let's add the service and Web API function, they are easy, see:

// Delete Customer in service
customerService.delete = function (id) {
   var deferred = $q.defer();
   return $http.post(baseUrl + "delete/" + id)
         .success(function (data) {
        deferred.resolve();
    }).error(function (error) {
       deferred.reject(error);
       })
       return deferred.promise;
}

// Web API Method
[Route("api/Customer/delete/{id}")]
[HttpPost]
public void DeleteCustomer(int Id)
{
    var customer = db.Customers.FirstOrDefault(x => x.CustomerId == Id);
    db.Customers.Remove(customer);
    db.SaveChanges();
}

Whatever the features on the left pan is completed, now time to code to show the detail on selecting any customer from left pan by using the ui-state than redirecting to a new page, if you can go back and see the code what we written to show the detail, just and ng-click on an anchor tag ng-click="customerDetail(customer.CustomerId)".

For it we need a Web API method to get the data from database by customer Id and a service to call this method and a method on controller to call from page, so let's add all the three functions

// Add it in Web API Controller
[Route("api/Customer/detail/{id}")]
[HttpGet]
public Customer GetDetail(int Id)
{
    return db.Customers.FirstOrDefault(x=>x.CustomerId == Id);
}


// In Service: Customers detail by customer id
customerService.customerDetail = function (id) {
   var deferred = $q.defer();
   return $http.get(baseUrl + "detail/" + id)
        .success(function (data) {
            deferred.resolve(
                customerService.currentCustomer = data);
        })
   .error(function (error) {
       deferred.reject(error);
   })
   return deferred.promise;
}

// In Controller: Customer Detail
$scope.customerDetail = function (id) {
    if (!id) return;
    Customer.customerDetail(id)
    .then(function (data) {
        $scope.currentCustomer = Customer.currentCustomer;
        $state.go('customer.detail', { 'customerId': id });
    });
};
  • Whatever the data we are getting, we assigned that to a variable currentCustomer on scope
  • After getting the data, we are not redirecting to any other page
  • Just we are changing the current state, which will take care about the url change and
  • Bind the proper template and
  • Add that template to the child view

See the app.js file where we putt our all the configuration, let me add that code one more time here

.state('customer.detail', {
    url: '^/customer/detail/{customerId:[0-9]{1,5}}',
    templateUrl: 'app/customer/customerdetail.html',
    controller: 'customerCtrl'
})

Once we change the state, our application know from above code, which template it need to be bind, since it is a change view of customer view "customer.detail" it, check the ui-view in parent and attach this template into it. Is not it a nice feature, this is the feature for which UI Routing is popular for. Still we have not created our customerdetail.html template so let's add a HTML file in app/customer folder:

<div class="form-horizontal">
    <fieldset>
        <legend>Customer Detail</legend>

        <div class="col-lg-3">
            <img ng-src="{{currentCustomer.Image ? currentCustomer.Image : 'customerimages/default-image.png'}}"
                 class="img-circle"
                 alt='customer image' />
        </div>

        <div class="col-lg-8">
            <div class="form-group">
                <label for="inputEmail" class="col-lg-3 control-label">Full Name</label>
                <div class="col-lg-8">
                    <input type="text" class="form-control" ng-model="currentCustomer.FullName" disabled>
                </div>
                <div class="col-lg-1">
                    <button ng-click="editCustomer(currentCustomer.CustomerId)" class="btn btn-primary">Edit</button>
                </div>
            </div>
            <div class="form-group">
                <label for="inputEmail" class="col-lg-3 control-label">Address</label>
                <div class="col-lg-8">
                    <input type="text" class="form-control" ng-model="currentCustomer.Address" disabled>
                </div>
            </div>
            <div class="form-group">
                <label for="inputEmail" class="col-lg-3 control-label">City</label>
                <div class="col-lg-8">
                    <input type="text" class="form-control" ng-model="currentCustomer.City" disabled>
                </div>
            </div>
            <div class="form-group">
                <label for="inputEmail" class="col-lg-3 control-label">Country</label>
                <div class="col-lg-8">
                    <input type="text" class="form-control" ng-model="currentCustomer.Country" disabled>
                </div>
            </div>
            <div class="form-group">
                <label for="inputEmail" class="col-lg-3 control-label">Zip Code</label>
                <div class="col-lg-8">
                    <input type="text" class="form-control" ng-model="currentCustomer.ZipCode" disabled>
                </div>
            </div>
            <div class="form-group">
                <div class="col-lg-8 col-lg-offset-3">
                    <a ui-sref="customer.detail.contact({contactCustomerId: currentCustomer.CustomerId})">Contact Detail</a>
                </div>
            </div>            
        </div>

    </fieldset>
</div>

<div ui-view></div>

If you can note at the very last div, it uses another ui-view to hold child view on it, in which we will show the contact list for this customer. This template have a button to edit the current customer and at the top of the page to add new customer. Which we will see in our next article because I am using here a modal popup so want to give full detail that how to open a modal popup and pass values from current controller to modal popup controller but before completing this article I want to show the code, how we can render contact detail list.

How to read the customer Id from url, it's simple, we need to inject $stateParams and call the parameter name which we defined in our config file, in this case customerId

var customerId = $stateParams.customerId;

See in above code how we are changing the state from an anchor tag and pass current customer Id as parameter

<a ui-sref="customer.detail.contact({customerId: currentCustomer.CustomerId})">
   Contact Detail
</a>
  • Note: ui-sref rather then href
  • contactCustomerId is parameter we defined in configuration
  • Once user click this link it call the configured controller and bind the defined template

Let's see this configuration:

.state('customer.detail.contact', {
    url: '^/customer/detail/contact/{customerId:[0-9]{1,5}}',
    templateUrl: 'app/customer/contact.html',
    controller: 'customerContactCtrl'
})
  • Contact is the child of Detail which is the child of customer customer.detail.contact
  • template: contact.html, which we will create in a moment
  • controller: customerContactCtrl, a separate controller which we are going to create now

Controller and HTML for the contact detail:

Create a separate js file for controller:

// Conatact controller
var app = angular.module("MyApp");
app.controller("customerContactCtrl", ['$scope', '$state', '$stateParams', '$modal', '$log', 'Customer', 
   function ($scope, $state, $stateParams, $modal, $log, Customer) {

    var customerId = $stateParams.customerId;

    $scope.customerContacts = function (id) {
        return Customer.customerContacts(id)
        .then(function (data) {
            $scope.contacts = Customer.conntacts;
        });
    };
    $scope.customerContacts(customerId);
}]);


// Customers Contact: Created a function in customer service    
customerService.customerContacts = function (id) {
   var deferred = $q.defer();
   return $http.get(contactBaseUrl + "ByCustomerId/" + id)
        .success(function (data) {
            deferred.resolve(customerService.conntacts = data);
        }).error(function (error) {
            deferred.reject(error);
        })
   return deferred.promise;
}

Create a separate controller Contact in Web API and add following code in it

[Route("api/Contact/ByCustomerId/{Id}")]
public IEnumerable<Contact> GetCustomerContacts(int Id)
{
    var contacts = db.Contacts.Where(x => x.CustomerId == Id);
    return contacts;
}

Finally here is the Customer and Contact models to create the database tables:

public class Customer
{
    [Key]
    public int CustomerId { get; set; }
    [Required]
    public string FullName { get; set; }
    [Required]
    public string Address { get; set; }
    [Required]
    public string City { get; set; }
    [Required]
    public string ZipCode { get; set; }
    [Required]
    public string Country { get; set; }
    public string Image { get; set; }
}

public class Contact
{
    [Key]
    public int ContactId { get; set; }
    [Required]
    public int CustomerId { get; set; }

    [Required]
    public string Phone { get; set; }
    public string Fax { get; set; }
    public string Email { get; set; }
}

If you notice we have not written anything for progress bar or progress image to show while data is fetching from database, it is added in application for more detail you can see my previous article Angularjs Progress bar directive for entire application.

How to add new customer or edit existing customer: Angularjs CRUD with Web API, Entity Framework & Bootstrap modal popup part 2

Code is uploaded on GitHub so you can Download it from there and try.

Ali Adravi Having 13+ years of experience in Microsoft Technologies (C#, ASP.Net, MVC and SQL Server). Worked with Metaoption LLC, for more than 9 years and still with the same company. Always ready to learn new technologies and tricks.
  • angularjs
  • webapi
  • ui.router
  • entity-framework
  • modal-popup
By Ali Adravi On 28 Jul, 15  Viewed: 31,462

Other blogs you may like

Angularjs powerful paging on table and searching with delay and demo

Paging on table is the basic requirement and most of us look for some grid so it can provide the complete feature of paging and sorting but in Angularjs it is so easy that anyone can achieve it by writing some code. I checked and found most of the available code give the paging feature in for 1 to... By Ali Adravi   On 14 Aug 2015  Viewed: 20,947

Angularjs Cascading Dropdown/Select with demo

Cascading dropdown with angularjs is easier than MVC and ASP.Net. In Angularjs ng-change is the best event to hook the function to get the second dropdown list items. In this article, we will try to fill the state dropdown on selection change of country and then fill the city dropdown on change... By Ali Adravi   On 06 Aug 2015  Viewed: 57,780

Angularjs CRUD with Web API, Entity Framework & Bootstrap modal popup part 2

Add/Edid Customer in Modal Popup --- In previous article we discussed how to search, delete and show detail of customer and contact list. In this article we will try to open the modal popup to add new customer and edit an existing customer record. I divided article in two parts because of the... By Ali Adravi   On 29 Jul 2015  Viewed: 19,719

Custom Directives in AngularJS with example and demo

Angularjs custom directives are the common to avoid the duplicate code, it is easy powerful and really nice. There are already too many directive have been created by experts but as a developer we need to write our own to achieve our specific goal. ng-model, ng-if, ng-show, ng-repeat all these are... By Ali Adravi   On 29 Jul 2015  Viewed: 4,497

Angularjs Progress bar directive for entire application

Progress bar for Angularjs application by using directives is simple and real easy. I was googling and could not found any directive way, if you will check most of the people says before call the http method open the progress bas and close once you get the result or get any error. Do you think it... By Ali Adravi   On 24 Jul 2015  Viewed: 14,201