64

I have an HTML5 date input and I would like its value to be set to the value of the date property in my model by default. I'm not too fussy about formatting since Chrome seems to decide that for me anyway based on my locale, but ideally the format would be consistently dd/MM/yyyy.

Fiddle

This is how I set up my input:

<input type="date" 
       ng-model="date" 
       value="{{ date | date: 'yyyy-MM-dd' }}" />

This works fine on Chrome, and I see the following by default:

Chrome displaying the formatted value of the date

(I still don't quite understand why the value had to be given in yyyy-MM-dd, if Chrome still formats it based on my locale, but that's a different question.)

My issue is with Firefox not showing the date's value in the way I've specified. I think this has to do with binding the input to the date model, because I can specify pretty much any string in the value attribute, and I will still see the long date string in the input by default:

Firefox showing datestring

If I remove ng-model="date" from the input tag, Firefox nicely displays any value I give it. I didn't think the model that an input was bound to actually had any effect on its default value?

I understand the date input isn't supported universally, but seeing as it's supposed to fall back on a simple text input, I don't see why its value won't simply be 2013-08-05, as specified by angular's date filter.

So, how do I get Firefox to accept my formatted value in the date input?


NOTE After the edits have been done by the user, I will of course perform validation and convert each date input value into a proper Date object. Not sure if this is relevant to the question, but putting it out there just in case, because the input formats would obviously need to be consistent for the date conversion to work the same in all browsers. Problematic, of course, with Chrome deciding the input format for me...

Elise
  • 5,086
  • 4
  • 36
  • 51
  • Have you tried with the correct format at the initialisation (like here :http://jsfiddle.net/DotDotDot/Vq65z/7/ ) ? it could be something like : `$scope.date = $filter('date')(new Date(),'MM/dd/yyyy') ` – DotDotDot Aug 05 '13 at 15:30
  • @DotDotDot This works fine because the date in that case will be a string. However, I prefer the date to be an actual javascript date object because I need to manipulate it in all kinds of ways, and it is also more semantic. But basically your suggestion is my current hack, yes. I am using a temporary string for both the model the input is bound to and its value, and converting it to a date on form submit. – Elise Aug 05 '13 at 15:37
  • 2
    I don't agree with the closure as it's on topic, describes the problem with code to reproduce it, and states what the problem is. – forforf Jan 03 '14 at 20:29

7 Answers7

73

The problem is that value is ignored when ng-model is present.

Firefox, which doesn't currently support type="date", will convert all the values to string. Since you (rightly) want date to be a real Date object and not a string, I think the best choice is to create another variable, for instance dateString, and then link the two variables:

<input type="date" ng-model="dateString" />
function MainCtrl($scope, dateFilter) {
    $scope.date = new Date();

    $scope.$watch('date', function (date)
    {
        $scope.dateString = dateFilter(date, 'yyyy-MM-dd');
    });

    $scope.$watch('dateString', function (dateString)
    {
        $scope.date = new Date(dateString);
    });
}

Fiddle

The actual structure is for demonstration purposes only. You'd be better off creating your own directive, especially in order to:

Please notice that I've used yyyy-MM-dd, because it's a format directly supported by the JavaScript Date object. In case you want to use another one, you must make the conversion yourself.


EDIT

Here is a way to make a clean directive:

myModule.directive(
    'dateInput',
    function(dateFilter) {
        return {
            require: 'ngModel',
            template: '<input type="date"></input>',
            replace: true,
            link: function(scope, elm, attrs, ngModelCtrl) {
                ngModelCtrl.$formatters.unshift(function (modelValue) {
                    return dateFilter(modelValue, 'yyyy-MM-dd');
                });

                ngModelCtrl.$parsers.unshift(function(viewValue) {
                    return new Date(viewValue);
                });
            },
        };
});

Fiddle

That's a basic directive, there's still a lot of room for improvement, for example:

  • allow the use of a custom format instead of yyyy-MM-dd,
  • check that the date typed by the user is correct.
Community
  • 1
  • 1
Blackhole
  • 20,129
  • 7
  • 70
  • 68
  • Thanks, I'm beginning to think myself that some intermediate string variable needs to replace the model bound to my date input. You say I should create directives for this to isolate the string variable -- do I understand it would be something along the lines of updating my actual `date` model when the input's value changes, and not binding the input to a model at all? – Elise Aug 05 '13 at 16:07
  • Cool, I was just about to start using __[this](http://www.benlesh.com/2012/12/angular-js-custom-validation-via.html)__ as a rough guide, looks like the technique is more or less the same! – Elise Aug 05 '13 at 16:36
  • You don't need `template` and `replace` if you already use a text input. For example I am using an angular-ui-datetimepicker which is a directive applied to a text input. I used the same code and it works. – Pavel Nikolov Jan 30 '14 at 20:56
  • Great answer. Thanks you very much for sharing with us. my vote + – imdadhusen Jun 25 '14 at 13:16
  • isn't this `link the two variables` way to get `inf digest` exception? – vp_arth Dec 29 '14 at 04:40
  • @vp_arth No, because `dateFilter(new Date(dateString), 'yyyy-MM-dd') === dateString` and `new Date(dateFilter(date, 'yyyy-MM-dd')) === date` after a few iterations. There are never more than two digest cycles. – Blackhole Dec 29 '14 at 14:18
  • `new Date(...)` can't to be equal to date, because it's a new object. I check now: `new Date(2014, 11, 30) === new Date(2014, 11, 30)` is false. But yes, it's stopped, when strings equal... – vp_arth Dec 30 '14 at 05:11
  • anyway, I think something like `$scope.date.setTime((new Date(datestr)).getTime());` will be much better, because we don't replace `Date` object, just modify it. – vp_arth Jan 12 '15 at 05:13
  • 1
    @vp_arth This answer need to be rewritten anyway, in particular to make the edit less apparent. I'll take your comments into account for that, thank you very much. – Blackhole Jan 12 '15 at 10:57
  • Nice directive approach, but I think it would be better directive to be attribute type directive, not element type. – karaxuna Feb 09 '15 at 20:49
12

Why the value had to be given in yyyy-MM-dd?

According to the input type = date spec of HTML 5, the value has to be in the format yyyy-MM-dd since it takes the format of a valid full-date which is specified in RFC3339 as

full-date = date-fullyear "-" date-month "-" date-mday

There is nothing to do with Angularjs since the directive input doesn't support date type.

How do I get Firefox to accept my formatted value in the date input?

FF doesn't support date type of input for at least up to the version 24.0. You can get this info from here. So for right now, if you use input with type being date in FF, the text box takes whatever value you pass in.

My suggestion is you can use Angular-ui's Timepicker and don't use the HTML5 support for the date input.

Community
  • 1
  • 1
zs2020
  • 53,766
  • 29
  • 154
  • 219
  • I'll definitely consider Angular's timepicker if I can't crack through it on my own, although I'm wondering about the support for custom styling... – Elise Aug 05 '13 at 16:40
3

You can use this, it works fine:

<input type="date" class="form1"  
  value="{{date | date:MM/dd/yyyy}}"
  ng-model="date" 
  name="id" 
  validatedateformat 
  data-date-format="mm/dd/yyyy"
  maxlength="10" 
  id="id" 
  calendar 
  maxdate="todays"
  ng-click="openCalendar('id')">
    <span class="input-group-addon">
      <span class="glyphicon glyphicon-calendar" ng-click="openCalendar('id')"></span>
    </span>
</input>
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

In my case, I have solved this way:

$scope.MyObject = // get from database or other sources;
$scope.MyObject.Date = new Date($scope.MyObject.Date);

and input type date is ok

0

I've used ng-change:

Date.prototype.addDays = function(days) {
  var dat = new Date(this.valueOf());
  dat.setDate(dat.getDate() + days);
  return dat;
}

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

app.controller('DateController', ['$rootScope', '$scope',
  function($rootScope, $scope) {
    function init() {
      $scope.startDate = new Date();
      $scope.endDate = $scope.startDate.addDays(14);
    }


    function load() {
      alert($scope.startDate);
      alert($scope.endDate);
    }

    init();
    // public methods
    $scope.load = load;
    $scope.setStart = function(date) {
      $scope.startDate = date;
    };
    $scope.setEnd = function(date) {
      $scope.endDate = date;
    };

  }
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-controller="DateController">
  <label class="item-input"> <span class="input-label">Start</span>
    <input type="date" data-ng-model="startDate" ng-change="setStart(startDate)" required validatedateformat calendar>
  </label>
  <label class="item-input"> <span class="input-label">End</span>
    <input type="date" data-ng-model="endDate" ng-change="setEnd(endDate)" required validatedateformat calendar>
  </label>
  <button button="button" ng-disabled="planningForm.$invalid" ng-click="load()" class="button button-positive">
    Run
  </button>
</div <label class="item-input"> <span class="input-label">Start</span>
<input type="date" data-ng-model="startDate" ng-change="setStart(startDate)" required validatedateformat calendar>
</label>
<label class="item-input"> <span class="input-label">End</span>
  <input type="date" data-ng-model="endDate" ng-change="setEnd(endDate)" required validatedateformat calendar>
</label>
hypery2k
  • 1,681
  • 15
  • 20
0

Check this fully functional directive for MEAN.JS (Angular.js, bootstrap, Express.js and MongoDb)

Based on @Blackhole ´s response, we just finished it to be used with mongodb and express.

It will allow you to save and load dates from a mongoose connector

Hope it Helps!!

angular.module('myApp')
.directive(
  'dateInput',
  function(dateFilter) {
    return {
      require: 'ngModel',
      template: '<input type="date" class="form-control"></input>',
      replace: true,
      link: function(scope, elm, attrs, ngModelCtrl) {
        ngModelCtrl.$formatters.unshift(function (modelValue) {
          return dateFilter(modelValue, 'yyyy-MM-dd');
        });

        ngModelCtrl.$parsers.push(function(modelValue){
           return angular.toJson(modelValue,true)
          .substring(1,angular.toJson(modelValue).length-1);
        })

      }
    };
  });

The JADE/HTML:

div(date-input, ng-model="modelDate")
0

If using Angular Material Design, you can use the datepicker component there and this will work in Firefox, IE etc.

https://material.angularjs.org/latest/demo/datepicker

Fair warning though - personal experience is that there are problems with this, and seemingly it is being re-worked at present. See here:

https://github.com/angular/material/issues/4856

John Rix
  • 6,271
  • 5
  • 40
  • 46