Murali Krishna
Murali Krishna

Reputation: 133

Angular-UI date picker is in invalid state when specified the date format as 'd-M-yyyy' and ng-model with a string value as "2014-08-31T00:00:00Z"

I am getting a date time value from asp.net mvc controller as "2014-08-31T00:00:00Z". When I bind this value to my angular-ui datepicker control it's state is showing as ng-invalid ng-invalid-date.

I am getting the date-format as well from the mvc controller so I am binding the date-format as well in my html.

When I am debugging the ui-bootstrap-tpls.js (latest version) file at line 1807

enter image description here

It's always coming as undefined. I have tried so many alternatives but I am unable to succeed. :(

javascript does't convert angular ui datepicker date to UTC correctly

So please give some thoughts and suggest me how can I solve this problem.

Thanks & Regards, N.Murali Krishna.

Upvotes: 7

Views: 18652

Answers (7)

Morteza QorbanAlizade
Morteza QorbanAlizade

Reputation: 1550

I solved the problem by converting string to valid javascript formt in directive:

see here: Datepicker-popup formatting not working when value set initially in scope

Upvotes: 1

DJ Singh
DJ Singh

Reputation: 11

Just wanted to add my solution to this stack.

app.directive('formatDate', function (dateFilter, uibDateParser, uibDatepickerPopupConfig) {
    return {
        restrict: 'A',
        priority: 1,
        require: 'ngModel',
        link: function (scope, element, attr, ngModel) {
            var dateFormat = attr.uibDatepickerPopup || uibDatepickerPopupConfig.datepickerPopup;
            ngModel.$validators.date = function(modelValue, viewValue) {
                var value = viewValue || modelValue;

                if (!attr.ngRequired && !value) {
                    return true;
                }

                if (angular.isNumber(value)) {
                    value = new Date(value);
                }
                if (!value) {
                    return true;
                } else if (angular.isDate(value) && !isNaN(value)) {
                    return true;
                } else if (angular.isString(value)) {
                    var date = new Date(value);
                    return !isNaN(date);
                } else {
                    return false;
                }
            }
            ngModel.$formatters.push(function(value) {
                return new Date(value);
            });
        }
    };
});

Upvotes: 1

user147677
user147677

Reputation: 491

To use formatted date string instead of Date object as ng-model for Angular Datepicker, you need to create a wrapper directive. The wrapper directive will parse your string into Date object and pass it to the datepicker. When you select a date, it is converted from Date back into string. Here is an example (Plunker):

(function () {
    'use strict';

    angular
        .module('myExample', ['ngAnimate', 'ngSanitize', 'ui.bootstrap'])
        .controller('MyController', MyController)
        .directive('myDatepicker', myDatepickerDirective);

    MyController.$inject = ['$scope'];

    function MyController ($scope) {
      $scope.dateFormat = 'dd MMMM yyyy';
      $scope.myDate = '30 Jun 2017';
    }

    myDatepickerDirective.$inject = ['uibDateParser', '$filter'];

    function myDatepickerDirective (uibDateParser, $filter) {
        return {
            restrict: 'E',
            scope: {
                name: '@',
                dateFormat: '@',
                ngModel: '='
            },
            required: 'ngModel',
            link: function (scope) {

                var isString = angular.isString(scope.ngModel) && scope.dateFormat;

                if (isString) {
                    scope.internalModel = uibDateParser.parse(scope.ngModel, scope.dateFormat);
                } else {
                    scope.internalModel = scope.ngModel;
                }

                scope.open = function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    scope.isOpen = true;
                };

                scope.change = function () {
                    if (isString) {
                        scope.ngModel = $filter('date')(scope.internalModel, scope.dateFormat);
                    } else {
                        scope.ngModel = scope.internalModel;
                    }
                };

            },
            template: [
                '<div class="input-group">',
                    '<input type="text" readonly="true" style="background:#fff" name="{{name}}" class="form-control" uib-datepicker-popup="{{dateFormat}}" ng-model="internalModel" is-open="isOpen" ng-click="open($event)" ng-change="change()">',
                    '<span class="input-group-btn">',
                        '<button class="btn btn-default" ng-click="open($event)">&nbsp;<i class="glyphicon glyphicon-calendar"></i>&nbsp;</button>',
                    '</span>',
                '</div>'
            ].join('')
        }
    }

})();
<!DOCTYPE html>
<html>

  <head>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-sanitize.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.5.0.js"></script>
    <script src="example.js"></script>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  </head>

  <body ng-app="myExample">
    <div ng-controller="MyController">
      <p>
        Date format: {{dateFormat}}
      </p>
      <p>
        Value: {{myDate}}
      </p>
      <p>
        <my-datepicker ng-model="myDate" date-format="{{dateFormat}}"></my-datepicker>
      </p>
    </div>
  </body>

</html>

Upvotes: 2

user1839114
user1839114

Reputation: 61

This is best solution i found so far:

.directive('uibDatepickerPopup', function (dateFilter, uibDateParser, uibDatepickerPopupConfig) {
    return {
        restrict: 'A',
        priority: 1,
        require: 'ngModel',
        link: function (scope, element, attr, ngModel) {
            var dateFormat = attr.uibDatepickerPopup || uibDatepickerPopupConfig.datepickerPopup;
            ngModel.$validators.date = function(modelValue, viewValue) {
                var value = viewValue || modelValue;

                if (!attr.ngRequired && !value) {
                    return true;
                }

                if (angular.isNumber(value)) {
                    value = new Date(value);
                }
                if (!value) {
                    return true;
                } else if (angular.isDate(value) && !isNaN(value)) {
                    return true;
                } else if (angular.isString(value)) {
                    var date = uibDateParser.parse(value, dateFormat);
                    return !isNaN(date);
                } else {
                    return false;
                }
            };
        }
    };
});

Upvotes: 6

icfantv
icfantv

Reputation: 4643

A couple of notes here:

  1. First, the datepicker directive requires that the ng-model be a Date object. This is documented here.
  2. Second, the solution posted (and accepted) by Chet above is VERY heavy-handed as it takes EVERY date string received in an HTTP response and converts it to a Date object if it matches a hard-coded pattern. This is not flexible nor is it easily testable. It will not be the right solution for most people.

If, in your system, global date string conversion is the right behavior, the proper Angular design would be to create a service that does this for you. This leads me to...

We've (Angular UI Bootstrap) provided a mechanism for converting date strings into Date objects via the dateParser service. You can see the source code here. NB: this service name becomes deprecated and changed to uibDateParser with the 0.14.0 release.

Upvotes: 6

Jiran Dowlati
Jiran Dowlati

Reputation: 21

I don't have enough reputation to comment under Chet's answer but thanks so much this solution worked for me as well! I don't know your Github handle to tag you in the issue but I submitted a Github issue under Angular UI to help others find the solution and hopefully get Angular UI guys to look into it. Thanks again!

https://github.com/angular-ui/bootstrap/issues/4554

Upvotes: 2

Chet
Chet

Reputation: 3729

I had the same problem. The issue is that Angular is expecting an actual date object, not a string representation of the date. After doing a bunch of research I ended up adding a transformReponse to the $httpProvider which checks all string objects to see if they can be converted to a date, and if so actually converting them.

angular.module('test')
.config(['$httpProvider', function ($httpProvider) {

    // ISO 8601 Date Pattern: YYYY-mm-ddThh:MM:ss
    var dateMatchPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

   var convertDates = function (obj) {
      for (var key in obj) {
         if (!obj.hasOwnProperty(key)) continue;

         var value = obj[key];
         var typeofValue = typeof (value);

         if (typeofValue === 'object') {
            // If it is an object, check within the object for dates.
            convertDates(value);
         } else if (typeofValue === 'string') {
            if (dateMatchPattern.test(value)) {
               obj[key] = new Date(value);
            }
         }
      }
   }

   $httpProvider.defaults.transformResponse.push(function (data) {
      if (typeof (data) === 'object') {
         convertDates(data);
      }

      return data;
   });
}])

Upvotes: 11

Related Questions