Single Page Application with MVC and AngularJS part 3

In part 2 we created a single page application by layering an AngularJS application on top of an existing MVC application.

Although the solution works as expected there are a few things that can be improved. For example when a user navigates to a listing or edit page for the first time MVC will load the data server side and return it to the users browser. The Angular app on the client side will then immediately replace that data again because we are using the same routes for both applications.

This is not a problem after the initial page load because Angular will then handle everything client side, but it is inefficient and can create an annoying flicker for the user.

To fix this MVC needs to tell angular that it has loaded the data on page load already so don’t bother fetching it again asynchronously. Because we have separate views for MVC this is easy enough; all we need to do is a hidden field to the view that MVC will render to say that the data has already been loaded by the server. 

@using aml.SinglePageApplicationMVC.Lib
@model IEnumerable<Customer>

@{
    ViewBag.Title = "Customers";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@Html.Partial("~/Views/Partials/_ShowCustomers.cshtml", Model)
<input type="hidden" id="hdnServerLoaded" value="true" />

This could be made a bit smarter and more generic with a custom Html helper rather than repeatedly hard coding in all the relevant views. The Angular controller can then check to see if the hidden field is present before loading any data again.

app.controller('ShowCustomersController', ['$scope', '$location', '$http', 'CustomerService', function ($scope, $location, $http, CustomerService) {

    var isServerLoaded = document.getElementById('hdnServerLoaded');

    if (!isServerLoaded) {
        loadData();
    }

The next issue is getting AngularJS validation to work client side along with MVC validation. The example deliberately omitted any validation to keep the first tutorial parts a reasonable size.

One of the great features of MVC is the use of data attributes to define server side and client side form validation in a single location. Entity classes can then be decorated to indicate required Model properties like so:

public class Customer {
    public int Id { get; set; }

    [Required]
    public string FirstName { get; set; }
    [Required]
    public string Surname { get; set; }
    [Required]
    public string Telephone { get; set; }
}

When using the standard Html helpers along with the unobtrusive JavaScript library everything just works as you would expect. All you need to do is output the validation messages in your views:

<div class="form-group">
    @Html.LabelFor(model => model.FirstName, "First Name", htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control", data_ng_model = "Customer.FirstName" } })
        @Html.ValidationMessageFor(model => model.FirstName, "Please enter your First Name")
    </div>
</div>

The problem is that the angular form does not know or understand the attributes added to achieve the client side validation. It would be nice to avoid having to replicate model specific validation in the Angular controller and simply control it from what is already defined in our MVC model classes.

We can achieve this by adding a couple of directives to allow Angular to use the mark-up generated by the standard Html helpers.

app.directive('valRequired', function () {
    return {
        restrict: 'A',
        require: 'ngModel',

        link: function (scope, elem, attrs, ngModel) {
             scope.$watch(attrs.ngModel, function (value) {
                 var isValid = value != null && value.length > 0;
                 ngModel.$setValidity(attrs.ngModel, isValid);
             });
        }
    };
});

app.directive('valmsgFor', function () {
    return {
        restrict: 'A',
        require: '^form',

        link: function (scope, elem, attrs, ctrl) {
            var modelField = ctrl.$name + '.' + attrs.valmsgFor + '.$valid';
            scope.$watch(modelField, function (isValid) {
                if (isValid) {
                    elem.attr('class', 'field-validation-valid');
                } else {
                    elem.attr('class', 'field-validation-invalid');
                }
            });
        }
    };
});

Once in place we then just need to add in some styling to ensure the validation messages are displayed only when appropriate.

form.ng-submitted input.ng-invalid {
    border-color:red;
    color:red;
}
.field-validation-valid,
.field-validation-invalid {
    display:none;
}
form.ng-submitted .field-validation-invalid {
    display:inline;
    color:red;
}

Now the forms will validate according to the required rules defined in the Model classes, for other validation types additional directives will be required but once in place all you need to do is add the attributes to the classes. The full code project solution can be found on github.