Single Page Application with AngularJS in .NET

I tried to learn about SPAs from online articles and there are some good ones for beginners with mainly CRUD operations for only one database table. In the real world, there are many tables you need to retrieve data from and insert into and I couldn't find any such article. So, I have decided to create on my own and I thank my project manager for asking me to show a sample using the AngularJs framework.

This is a two-part demo, in the first part, I will be showing how to insert a record into a database get all the records from the database, and display them. Part 2 will show the delete and update operations.

Introduction

These days users want faster web applications with a Rich UI and to be able to access apps from multiple gadgets (like Tablets, Mobiles, and so on). HTML5 and jQuery or JavaScript can be used to build Single Page Applications (SPAs).

SPAs

SPAs are web applications that fit on a single page and that provide a more fluid user experience, just like the desktop with a rich UI. SPAs are web apps that load a single HTML page and dynamically update the page as the user interacts with the app.

SPA vs Traditional Web Apps
 

Traditional Web Apps

You can see in the following image the traditional apps. Whenever the user makes a request, it goes back to the server and renders a new page with a page reload.

Magazine

Source: https://msdn.microsoft.com/

SP App

In a SPA when the user requests a page it will load dynamically, calls to the server will be done using Ajax, and data is retrieved by JSON. In the SPA the data rendering and routing are done on the client side and that makes it responsive just like desktop apps.

SP App

The following are the advantages of SPAs.

  • No page flicker. Native application feel.
  • Client-side routing and data rendering on the client side.
  • Data from the server is in JSON format.

The following are the disadvantages of SPAs.

  • User JavaScript must be enabled.
  • Security.

Creating a project in Visual Studio

In this demo, I have used Visual Studio 2012, .NET Framework 4.5, AngularJs, Bootstrap, and .mdf files for database operations. I have provided all the necessary screenshots and source code to download.

Open Visual Studio 2012 and create a project with MVC4 template, I have named the project SingePageAngularJS, but you can name it anything you like.

Create project

Adding jQuery and AngularJs

The following figure shows I installed Bootstrap, AngularJs, and Angular routing from NuGet packages. And added a new folder to the scripts folder named App and added a new JavaScript file called myApp. I will be adding AngularJs code in myApp later on.

 AngularJs

Now to add these installed files to the MVC project. Go To App_Start, then BundleConfig. Add the following lines as I have shown in the Red rectangle in the following.

BundleConfig

Now you need to ensure these files are added to your Master page (_Layout. cshtml) located in the Views -> Shared folder of the application. I always put my jQuery or required files inside the head tag of the project, to make it accessible to your entire application as in the following image.

JQuery

Now we have added jQuery, Angular, Angular routing, Bootstrap CSS, and the bootstrap.js files to our layout page and we need to be sure they are rendered on our browser properly without any errors. To double-check, run the application in the Chrome browser and press F12 to open the browser tools. Check that all the files are loaded by clicking on the Sources tab and check that there are no errors in the console tab. The following confirms that all the files are loaded properly with no errors in the console tab.

Console tab

Setting up Database

I will add a .mdf file for the database operations and name it AngularDB.mdf. In the .mdf file, I created the two tables EmpDetails and EmpAddress and the scripts are provided below. The EmpDetails table contains employee information and EmpAddress contains the employee's multiple address. Between these two tables we have a foreign key relation with EmpId because whenever you select an employee I want to display all the addresses of the specific employee.

EmpDetails Table

CREATE TABLE [dbo].[EmpDetails] (
   [EmpID] INT IDENTITY (1, 1) NOT NULL,
   [EmpName] VARCHAR (50) NULL,
   [EmpPhone] VARCHAR (50) NULL,
   PRIMARY KEY CLUSTERED ([EmpID] ASC)
);

EmpAddress Table

CREATE TABLE [dbo].[EmpAddress] (
   [EmpAddressId] INT IDENTITY (1, 1) NOT NULL,
   [Address1] VARCHAR (500) NULL,
   [Address2] VARCHAR (500) NULL,
   [Address3] VARCHAR (500) NULL,
   [EmpID] INT NULL,
   PRIMARY KEY CLUSTERED ([EmpAddressId] ASC),
   FOREIGN KEY ([EmpID]) REFERENCES [dbo].[EmpDetails] ([EmpID])
);

Note. In this application, my concentration is not only on CRUD operations but also on showing how to display data from multiple tables to the UI and pass data to multiple tables.

MVVM Pattern

For this demo, I will be using the MVVM pattern for both the AngularJs script and Visual Studio. The following image shows I have created a folder called ViewModels (it has references of multiple models) and added two models in the model's folder. I named them EmpDetailsModel and EmpAddressModel.

MVVM Pattern

AngularJs Code

In the previous section, we did some database-related stuff. Now we jump into the main topic of this demo.

In Adding jQuery and AngularJs: We have created the myApp.js file and left it empty, now just copy and paste the following code into it.

angular.module('App', ['AngularDemo.EmpAddController', 
                      'AngularDemo.AddressController', 
                      'AngularDemo.DeleteController'
])

.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

    $routeProvider.when('/', {
        templateUrl: '/Home/AddEmployee',
        controller: 'EmpAddCtrl',
    });
    $routeProvider.when('/Edit', {
        templateUrl: '/Home/EditEmployee',
        controller: 'EditCtrl'
    });
    $routeProvider.when('/Delete', {
        templateUrl: '/Home/DeleteEmployee',
        controller: 'DeleteCtrl'
    });
    $routeProvider.otherwise({
        redirectTo: '/'
    });
    // Specify HTML5 mode (using the History APIs) or HashBang syntax.
    $locationProvider.html5Mode(false).hashPrefix('!');
}]);

// Add Employee Controller
angular.module('AngularDemo.EmpAddController', ['ngRoute'])
.controller('EmpAddCtrl', function ($scope, $http) {

    $scope.EmpAddressList = {};
    $http.get('/Home/ShowEmpList').success(function (data) {
        $scope.EmpAddressList = data;    
    });   
    $scope.EmpDetailsModel =  
    {
        EmpID: '',
        EmpName: '',
        EmpPhone: ''
    };
    $scope.EmpAddressModel = 
    {
        Address1: '',
        Address2: '',
        Address3: ''
    };
    $scope.EmployeeViewModel = {
        empDetailModel: $scope.EmpDetailsModel,
        empAddressModel: $scope.EmpAddressModel
    };
    $scope.AddEmployee = function () {
        //debugger; 
        $.ajax({
            url: '/Home/AddEmpDetails',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            traditional: true,
            data: JSON.stringify({ EmployeeViewModelClient: $scope.EmployeeViewModel }),
            success: function (data) {
                $scope.EmpAddressList.push(data[0]);
                $scope.$apply();
                //$scope.$apply();
                alert("Record is been added");
            }
        });
    };
});
// Address Controller
angular.module('AngularDemo.AddressController', ['ngRoute'])
.controller('EditCtrl', function ($scope, $http) {
    $scope.Message = "Edit in Part 2 is coming soon";
});
angular.module('AngularDemo.DeleteController', ['ngRoute'])
.controller('DeleteCtrl', function ($scope, $http) {
    $scope.Message = "Delete in Part 2 is coming soon";
});

Angular Code Explanation

angular.module('App', ['AngularDemo.EmpAddController', 
                       'AngularDemo.AddressController', 
                       'AngularDemo.DeleteController'
]);

Note. The angular. module is a global place for creating, registering, and retrieving Angular modules. All modules that should be available to an application must be registered using this mechanism.

The first parameter is the name of your Angular app, for our demo it is named "App".

The second parameter is an array of dependencies, we have only two dependencies, currently, they are:

'AngularDemo.EmpAddController', 'AngularDemo.AddressController' and 'AngularDemo.DeleteController'.

ng-app directive

Once you name your AngularJs you need to use it in your HTML page depending upon your requirements. For this demo, I have used ng-app in the _Layout page in the HTML tag as in the following code snippet:

App directive

Client-side Routing

So, we have defined the name of our AngularJs app and specified an array of dependencies. Now, let’s create the routing at the client side. For this, I have created three routes as in the following with templateUrl and controller.

config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

    $routeProvider.when('/', {
        templateUrl: '/Home/AddEmployee',
        controller: 'EmpAddCtrl',
    });
    $routeProvider.when('/Edit', {
        templateUrl: '/Home/EditEmployee',
        controller: 'EditCtrl'
    });
    $routeProvider.when('/Delete', {
        templateUrl: '/Home/DeleteEmployee',
        controller: 'DeleteCtrl'
    });
    $routeProvider.otherwise({
        redirectTo: '/'
    });
    // Specify HTML5 mode (using the History APIs) or HashBang syntax.
    $locationProvider.html5Mode(false).hashPrefix('!');

}]);

Whenever AngularJs finds the URL that was specified in routing, it goes to the corresponding AngularJs controller, where we need to create models and Ajax calls to the server. The following is the controller code.

// Add and display Employee Controller
angular.module('AngularDemo.EmpAddController', ['ngRoute'])
.controller('EmpAddCtrl', function ($scope, $http) {

    $scope.EmpDetailsModel = 
    {
        EmpID: '',
        EmpName: '',
        EmpPhone: ''
    };

    $scope.EmpAddressModel = 
    {
        Address1: '',
        Address2: '',
        Address3: ''
    };

    $scope.EmployeeViewModel = {
        empDetailModel: $scope.EmpDetailsModel,
        empAddressModel: $scope.EmpAddressModel
    };

    $scope.EmpAddressList = {};
    $http.get('/Home/ShowEmpList').success(function (data) {
        $scope.EmpAddressList = data;
    });
    
    $scope.AddEmployee = function () {
        $.ajax({
            url: '/Home/AddEmpDetails',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            traditional: true,
            data: JSON.stringify({ EmployeeViewModelClient: $scope.EmployeeViewModel }),
            success: function (data) {
                $scope.EmpAddressList.push(data[0]);
                $scope.$apply();
                alert("Record is been added");
            }
        });
    };
});

$scope is responsible for setting the model properties and the functions/behavior of the specific controller.

In the EmpAddCtrl controller, we have two models (EmpDetailsModeland EmpAddressModel) and ViewModel (EmployeeViewModel) where we will be passing this viewmodel to the server to save data to the database, using the$scope.AddEmployee function and $http.get will get the list of all records on pageloads.

Views

Now we are done with AngularJs. We need to use this in HTML pages. So, this is what my _layout page looks like.

_Layout.html

<!DOCTYPE html>
<html lang="en" ng-app="App">
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title - My ASP.NET MVC Application</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/angular")
    @Scripts.Render("~/bundles/CustomAngular")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <header>
        <nav class="navbar navbar-default navbar-static-top">
            <p class="container">
                <p class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                </p>
                <p id="navbar" class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                        <li class="active"><a href="#!/">Add</a></li>
                        <li><a href="#!/Address">Edit/Update</a></li>
                        <li><a href="#!/Delete">Delete</a></li>
                    </ul>
                </p>
                <!--/.nav-collapse -->
            </p>
        </nav>
    </header>
    <p id="body">
        @RenderSection("featured", required: false)
        <section class="content-wrapper main-content clear-fix">
            @RenderBody()
        </section>
    </p>

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

Index.html page

Just copy the following code and paste it into the index HTML page. ng-view is a placeholder where the partial views are dynamically loaded.

<p ng-view=""></p>

AddEmployee.html partial page

Copy the following code and paste it into your AddEmployee partial view. In this view, the user can add a new employee and address information and it also displays a list of employees in grid format.

The ng-model directive binds the value of HTML controls (input, select, and textarea) to the application data.

<p style="width: 50%; margin: 50px auto;">
    <table>
        <tr>
            <td>
                <strong>Employee Name:</strong>
            </td>
            <td>
                <input type="text" class="form-control" ng-model="EmpDetailsModel.EmpName" placeholder="Employee Name" />
            </td>
        </tr>
        <tr>
            <td>
                <strong>Employee Phone:</strong>
            </td>
            <td>
                <input type="text" class="form-control" ng-model="EmpDetailsModel.EmpPhone" placeholder="Employee Phone" />
            </td>
        </tr>
        <tr>
            <td>
                <strong>Address 1:</strong>
            </td>
            <td>
                <input type="text" class="form-control" ng-model="EmpAddressModel.Address1" placeholder="Address 1" />
            </td>
        </tr>
        <tr>
            <td>
                <strong>Address 2:</strong>
            </td>
            <td>
                <input type="text" class="form-control" ng-model="EmpAddressModel.Address2" placeholder="Address 2" />
            </td>
        </tr>
        <tr>
            <td>
                <strong>Address 3:</strong>
            </td>
            <td>
                <input type="text" class="form-control" ng-model="EmpAddressModel.Address3" placeholder="Address 3" />
            </td>
        </tr>

        <tr>
            <td>    
            </td>
            <td>
                <button type="button" ng-click="AddEmployee();" class="btn btn-primary">Save</button>
            </td>
        </tr>
    </table>
</p>
<hr style="color: black" />
<p style="width: 50%; margin: 50px auto;">
    <p class="panel panel-default">
        <!-- Default panel contents -->
        <p class="panel-heading"><b>Employee Details </b></p>
        <p class="table-responsive">
            <table id="EmployeeTable" class="table table-striped table-bordered table-hover table-condensed">
                <thead>
                    <tr>
                        <th>Employee Name</th>
                        <th>Employee Phone</th>
                        <th>Employee Address1</th>
                        <th>Employee Address2</th>
                        <th>Employee Address3</th>
                    </tr>
                </thead>
                <tbody>
                    <tr data-ng-repeat="Emp in EmpAddressList">
                        <td>{{Emp.empDetailModel.EmpName}}</td>
                        <td>{{Emp.empDetailModel.EmpPhone}}</td>
                        <td>{{Emp.empAddressModel.Address1}}</td>
                        <td>{{Emp.empAddressModel.Address2}}</td>
                        <td>{{Emp.empAddressModel.Address3}}</td>
                    </tr>
                    @*<tr ng-if="states.NewRow">*@   
                    <tr ng-if="EmpAddressList.length == 0">
                        <td class="text-center" colspan="4">There are no Employee details to display
                        </td>
                    </tr>
                </tbody>
            </table>
        </p>
    </p>
</p>

Output screens

Now we are done with all our coding. Run it by pressing F5, you can see the following screenshot. Initially, we don't have any data, therefore it displays There are no Employee details to display.

Output screens

Save Button

Enter the data of an employee and their address(es), when the user clicks the Save button, it triggers the AddEmployee function in AngularJs that calls the corresponding controller action. The following image shows that we have entered information.

Save Button

Once the user clicks on Save, the view model is passed from AngularJs to the MVC controller action. I have the following two screenshots to show the data being passed to the models (empDetailsModel and empAddressModel).

EmpDetailsModel

EmpAddressModel

After saving into the database you will get confirmation saying the record has been added.

Confirmation

Database

The data we have entered into the UI has been saved to the database.

Database

Conclusion

A single-page application will provide you with better performance compared to traditional web apps, but never compromise when it comes to security and you should think about security and implement sufficient measures for it before developing single-page applications.