9

I'm using Angular UI Bootstrap Datepicker: https://angular-ui.github.io/bootstrap/#/datepicker

When I render form using data received from the server, there is problem with datetime fields. My input datepicker looks like this:

<form name="itemForm">
    <input type="datetime" class="form-control" id="startedAt" name="startedAt"
           ng-model="item.startedAt"
           ng-click="open($event, 'startedAt')"
           uib-datepicker-popup="yyyy-MM-dd"
           is-open="datepickers.startedAt"
    />
</form>

My server returns response datetime as JSON string:

{    
   ...
   startedAt: "2015-05-29T02:00:00+0200"
}

When I assign response data to the model $scope.item = response;, datepicker input field is rendered correctly (correct date is selected and it's properly formatted in format I selected). The problem is that validation does not pass. I get:

itemForm.startedAt.$invalid == true

I noticed that data bound to the datepicker field should be Date object and not string (when I select new date from the datepicker, $scope.item.startedAt is a Date)

I managed to work around this issue and do this in the controller:

$scope.item = response;
$scope.item.startedAt = new Date($scope.item.startedAt);

It works this way... But I wouldn't like to manually convert string do date every time I get a response from the server. I tried to create a directive, that I can assign to the datepicker input field so it converts the ng-model for me:

.directive("asDate", function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, modelCtrl) {

            modelCtrl.$formatters.push(function (input) {

                var transformedInput = new Date(input);

                if (transformedInput != input) {
                    modelCtrl.$setViewValue(transformedInput);
                    modelCtrl.$render();
                }

                return transformedInput;
            });
        }
    }
})

Well it works, because now I can see Date object, when I output model in my view: {{item.startedAt}}. However still validation fails! I suspect this is some problem with me understanding how data flows between model and the view, and how UI Bootstrap hooks into it.

Also when I change my directive from $formatters.push to $formatters.unshift, validation works OK, but datepicker does not format my datetime (insted of nicely formattet yyyy-MM-dd I see ISO string inside the input)

3
  • 1
    Aww man we are having the exact same problem here!!! Commented Nov 18, 2015 at 14:58
  • @VictorParmar currently I'm doing this the way around - converting response string to Date object when receiving from server. And converting Date object to string when sending to server. All this done in Angular controller manually. Maybe I will just extract this logic as Angular service, but I don't think its possible to do with directive Commented Nov 18, 2015 at 15:11
  • 2
    yeah join the club - we ended up doing the same thing :) Commented Nov 19, 2015 at 15:32

3 Answers 3

1

This broke as of Angular.UI.Bootstrap v0.13.2 (8-2-2015) Downgrading to 0.13.1 works, which is where I'm stuck today.

Wesleycho says this was done intentionally https://github.com/angular-ui/bootstrap/issues/4690

I'm ready for other date pickers that support strings if anyone has a suggestion

...soon after posting this I went down a non-angular path that I'm not proud of, but it works for both HTML5 type="date" and uib-datepicker-popup. I have a regular expression that determines if a string resembles one of the two serialized date formats I've seen, and then I have a recursive javascript function to traverse a json tree and replace those strings with Date(). You would call it just before you put in $scope (or viewmodel) ...

$http.get("../api/comm/" + commId
    ).success(function (resp) {
        fixDates(resp);
        vm.comm = resp;
    });

(I need not check the string length, but I figured it would spare some cpu cycles by not running the regex if the string is obviously not a date)

//2015-10-01T00:00:00-04:00
//2015-11-20T18:15:56.6229516-05:00
var isDate = new RegExp("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{7})?-\\d{2}:00");

function fixDates(json) {
    for (i in json)
        if (typeof (json[i]) == "object")
            fixDates(json[i]);
        else if (typeof (json[i]) == "string" && (json[i].length == 25 || json[i].length == 33) && isDate.test(json[i]))
            json[i] = new Date(json[i]);
};
Sign up to request clarification or add additional context in comments.

1 Comment

when you are using javascript for-in loop you probable should also use hasOwnProperty()
1

As this is intentional behaviour of angular-ui-bootstrap datepicker (https://github.com/angular-ui/bootstrap/issues/4690), I ended up using Angular service/factory and moment library.

Service dateConverter can be injected globally to intercept all HTTP requestes/responses or only in desired controllers.

Here I use Restangular library to handle request to REST API, hence the response.plain() method which takes only object properties, and not Restangular methods/properties.

var Services = angular.module('app.Services', []);

Services
    .factory('dateConverter', ['dateFilter', function (dateFilter) {

        var dateConverter = {};

        dateConverter.prepareResponse = function (response) {
            for(prop in response.plain()) {
                if (response.hasOwnProperty(prop)) {
                    if(moment(response[prop], moment.ISO_8601, true).isValid()) {
                        response[prop] = new Date(response[prop]);
                    }
                }
            }
            return response;
        };

        dateConverter.prepareRequest = function (item) {
            for(prop in item.plain()) {
                if (item.hasOwnProperty(prop)) {
                    if(angular.isDate(item[prop])){
                         item[prop] = dateFilter(item[prop] , "yyyy-MM-ddTHH:mm:ssZ")
                    }
                }
            }
            return item;
        };

        return dateConverter;
    }])
;

Comments

1

you can transform String to Date in restangular transformer, something like this

RestangularConfigurer
  .addElementTransformer('<RESTRESOURCENAME>', false, function (element) {
      element.createDate = new Date(element.createDate);
      return element;
  })

1 Comment

Any guidance how to use it?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.