Sep 22, 2013

Cascading DropDownList with AngularJS and ASP.NET MVC

This article explains how to create cascading dropdownlists with AngularJS, Entity Framework Database First approach in ASP.NET MVC.

We are going to implement Country, State dropdownlists.

1. Assuming you have added "ADO.NET Entity Data Model"(.edmx) in the project for your database. Consider the following structure in edmx:

country state database

2. Install Angularjs Nuget package by running following command in package manager console:

Install-Package angularjs

3. add a new javascript file (cascade.js) in Scripts folder and define AngularJS module and controller:


var app = angular.module('myModule', []);
app.controller('myController', function ($scope, $http) {

//...will code here

});

4. Add a controller (HomeController.cs)


  public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }
	}

5. Add a view for Index method without layout, add angular and cascade javascript files references


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <script src="~/Scripts/angular.min.js"></script>
    <script src="~/Scripts/cascade.js"></script>
</head>
<body>
    <div data-ng-app="myModule">

        <form name="mainForm" data-ng-submit="sendForm()" data-ng-controller="myController" novalidate>
		 <div class="error">{{message}}</div>
		<!--   will code here -->
        </form>
    </div>
</body>
</html>


Case I: Ajax Oriented Approach:

In this approach, we fetch data using Ajax for child dropdownlist on selection change of parent dropdownlist.

Server Side Controller:


  public JsonResult GetCountries()
        {
            
            using (MyDatabaseEntities context = new MyDatabaseEntities()) {
                var ret = context.Countries.Select(x=>new { x.CountryId,x.CountryName}).ToList();
                return Json(ret, JsonRequestBehavior.AllowGet);             
            }            
        }
      
        [HttpPost]
        public JsonResult GetStates(int countryId)
        {
            using (MyDatabaseEntities context = new MyDatabaseEntities())
            {
                var ret = context.States.Where(x => x.CountryId == countryId).Select(x => new { x.StateId, x.StateName }).ToList();
                return Json(ret);
            }
        }
AngularJS Controller:

   GetCountries();  


    function GetCountries() {
        $http({
            method: 'Get',
            url: '/Home/GetCountries'
        }).success(function (data, status, headers, config) {
            $scope.countries = data;
        }).error(function (data, status, headers, config) {
            $scope.message = 'Unexpected Error';
        });
    }

    $scope.GetStates = function () {
        var countryId = $scope.country;
        if (countryId) {
            $http({
                method: 'POST',
                url: '/Home/GetStates/',
                data: JSON.stringify({ countryId: countryId })
            }).success(function (data, status, headers, config) {
                $scope.states = data;
            }).error(function (data, status, headers, config) {
                $scope.message = 'Unexpected Error';
            });
        }
        else {
            $scope.states = null;
        }
    }

View:



<div>
                <select data-ng-model="country" data-ng-options="c.CountryId as c.CountryName for c in countries" data-ng-change="GetStates()">
                    <option value="">-- Select Country --</option>
                </select>

                <select data-ng-model="state" data-ng-disabled="!states" data-ng-options="s.StateId as s.StateName for s in states">
                    <option value="">-- Select State --</option>
                </select>
            </div>

data-ng-change="GetStates()" : to call GetStates method on selection change of Country dropdown.

data-ng-disabled="!states" : to disable state dropdown if no country is selected.

Note: Don't worry about values in html structure of dropdown. It displays index(start with 0) of the option even you bind it with CountryId or StateId. I wasted more than one hour for this but found here that it is the behavior of AngularJS.

Case II: Common Dataset Approach:

In this apporach, the entire dataset (has data for both dropdownlists) is fetched once and it is filtered for child dropdownlist on selection change of parent dropdownlist.

Server Side Controller:


 public JsonResult GetAll()
        {
            using (MyDatabaseEntities context = new MyDatabaseEntities())
            {

                var ret = context.Countries.Include("State").Select(x => new { x.CountryName, x.CountryId, States = x.States.Select( y=>new { y.StateId,y.StateName }) }).ToList();
                return Json(ret, JsonRequestBehavior.AllowGet);
            }
        }

AngularJS Controller:


 //When you have entire dataset
    GetAll();
    function GetAll() {
        $http({
            method: 'Get',
            url: '/Home/GetAll'
        }).success(function (data, status, headers, config) {
            $scope.allItems = data;
        }).error(function (data, status, headers, config) {
            $scope.message = 'Unexpected Error';
        });
    }
View:

<div>
                <select data-ng-model="country2" data-ng-options="c as c.CountryName for c in allItems" data-ng-change="">
                    <option value="">-- Select Country --</option>
                </select>

                <select data-ng-model="state2" data-ng-disabled="!country2" data-ng-options="s.StateId as s.StateName for s in country2.States">
                    <option value="">-- Select State --</option>
                </select>
            </div>

Demo:

Conclusion:

We have seen how to bind dropdownlist using AngularJS and studied the cases When you have the entire Dataset or Ajax oriented approach for cascade dropdownlist in ASP.NET MVC.

Enjoy Programming !!