binding a date in angularjs using webapi and the bootstrap date picker

Jim picture Jim · Oct 8, 2014 · Viewed 7.9k times · Source

Given a WebApi2 service that returns json values like this:

{
    id: 1109,
    effectiveDate: "2014-10-05T00:00:00", // the date is a string (newtonsoft.json)
    text: "Duis et rhoncus nibh. Cras rhoncus cursus diam",
    fundSource: "Test"
}

I need the date to appear in the bound angular / bootstrap / date picker correctly.

I need to transform the date into the format yyyy-mm-dd (without the time) when binding it to an input box. Just a pointer to some documentation explaining what the correct way to serialize dates from the API to angular. I am sure that effectiveDate should actually be a Date object and not a string.

<input class="form-control" 
       type="text" 
       name="effectiveDate" 
       ng-model="consultation.effectiveDate" 
       data-date-picker="yyyy-mm-dd" 
       placeholder="Date" />

For completness, the service returning the json values looks like this:

app.factory('Service', ['$http', '$location', '$interpolate', function ($http, $location, $interpolate) {
    return {
        get: function (account) {
            var url = 'api/consultations/{account}';
            return $http
                .get(Api.format(url, { account: account }))
                .then(function (response) { return response.data; });
        }
    };
}]);

The controller method calls it like this:

service.get($scope.urlData.account).then(function(consultations) {
    $scope.consultations = consultations;
});

Answer

Mobiletainment picture Mobiletainment · Oct 30, 2015

I ran into the exact same problem and eventually solved it by writing an Angular http interceptor. It parses the server's response and converts all Datetime strings with ISO/UTC format into actual JavaScript date objects. This allows direct binding to the datepicker and solves validation issues.

Here's the client-side Angular code, consisting of a factory (the interceptor) and the config part for providing the http interceptor:

angular.module("app")
    .factory('dateInterceptor', function () {
        var regexIsoUtc = /^(\d{4}|\+\d{6})(?:-(\d{2}))(?:-(\d{2}))(?:T(\d{2})):(\d{2}):(\d{2})Z$/;

        function matchDate(dateString) {
            if (dateString.length === 20) {
                return dateString.match(regexIsoUtc);
            }
            return false;
        };

        function convertDateStringsToDates(object) {
            // ensure that we're processing an object
            if (typeof object !== "object") {
                return object;
            }

            for (var key in object) {
                if (!object.hasOwnProperty(key)) {
                    continue;
                }
                var value = object[key];

                // check for string properties with a date format
                if (typeof value === "string" && matchDate(value)) {
                    var date = new Date(value); // create the date from the date string
                    object[key] = date; // we're mutating the response directly
                } else if (typeof value === "object") {
                    convertDateStringsToDates(value); // recurse into object
                }
            }
            return null;
        }

        var interceptor = {
            'response': function (response) {
                if (response.data) {
                    convertDateStringsToDates(response.data);
                }
                return response;
            }
        };
        return interceptor;
    })

    .config(["$httpProvider", function ($httpProvider) {
        $httpProvider.interceptors.push('dateInterceptor'); // intercept responses and convert date strings into real dates
    }]);

On the server side I configured Newtonsoft.Json to serialize dates using the ISO format with UTC time zone, which is the format I test against in the interceptor:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        var formatters = GlobalConfiguration.Configuration.Formatters;
        var jsonFormatter = formatters.JsonFormatter;
        var settings = jsonFormatter.SerializerSettings;

        // serialize dates into ISO format with UTC timezone
        settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
        settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    }
}

The interceptor is thankfully based on the code from Automatic JSON date parsing with AngularJS and AngularJS HTTP Date Interceptor Factory.