96

I have an input defined as

<input class="datepicker" type="text" ng-model="clientForm.birthDate" />

Which is rigged up to be displayed elsewhere on the page:

<tr>
    <th>Birth Date</th>
    <td>{{client.birthDate|date:'mediumDate'}}</td>
</tr>

When the page loads the birth date is nicely formatted as something like Dec 22, 2009. However, when I look inside my <input> it's shown as Tue Dec 22 2009 00:00:00 GMT-0800 (Pacific Standard Time) which I guess is how JS renders Date objects as strings.

Firstly, how do I tell Angular to show the date in the <input> as something like 12/22/2009? I can't seem to apply |filters inside the ng-model attribute.

Secondly, as soon as I edit the date, even keeping it in it's original format, my other text (inside the <td>) doesn't seem to apply the |date filter anymore; it suddenly changes formats to match that of the input textbox. How do I get it to apply the |date filter every time the model changes?


Related questions:

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • I also had a problem on that, but came with simpler solution using standard js `Date()` functions: `$scope.departDate = new Date(); $scope.departTime = $scope.departDate.toTimeString().slice(0, 5);` And no need in other filters or tricky workarounds in AngularJS IMO. – boldnik Jun 22 '15 at 16:00
  • @boldnik what is departDate?? – Prabhat kumar Feb 17 '21 at 12:16
  • @Prabhatkumar it's a business logic part, just a custom variable name. – boldnik Feb 22 '21 at 08:16

9 Answers9

72

Use custom validation of forms http://docs.angularjs.org/guide/forms Demo: http://plnkr.co/edit/NzeauIDVHlgeb6qF75hX?p=preview

Directive using formaters and parsers and MomentJS )

angModule.directive('moDateInput', function ($window) {
    return {
        require:'^ngModel',
        restrict:'A',
        link:function (scope, elm, attrs, ctrl) {
            var moment = $window.moment;
            var dateFormat = attrs.moDateInput;
            attrs.$observe('moDateInput', function (newValue) {
                if (dateFormat == newValue || !ctrl.$modelValue) return;
                dateFormat = newValue;
                ctrl.$modelValue = new Date(ctrl.$setViewValue);
            });

            ctrl.$formatters.unshift(function (modelValue) {
                if (!dateFormat || !modelValue) return "";
                var retVal = moment(modelValue).format(dateFormat);
                return retVal;
            });

            ctrl.$parsers.unshift(function (viewValue) {
                var date = moment(viewValue, dateFormat);
                return (date && date.isValid() && date.year() > 1950 ) ? date.toDate() : "";
            });
        }
    };
});
magnattic
  • 12,638
  • 13
  • 62
  • 115
SunnyShah
  • 28,934
  • 30
  • 90
  • 137
  • 4
    I wanted to create a small example of formater and parser, Thanks to your question today I got the reason to do it. – SunnyShah Jan 23 '13 at 09:40
  • The 2nd method changes the input to `undefined NaN, NaN` as soon as you try typing in (creating an invalid date) which prevents you from finishing. First method seems to work pretty well though; I'll try that later tonight. Thank you for providing nice easy to follow examples! – mpen Jan 23 '13 at 16:39
  • 1
    @Mark, Fixed the Nan issue in second fiddle. ;) – SunnyShah Jan 23 '13 at 17:01
  • That's a *little* better but if I try typing something like "Jan 5" it slaps on the ", 2001" right away (as soon as the date is valid), which still mucks up your typing :-) I think the proper approach is to have the formatter not fire until a "change" event (rather than "keyup"/"input" or whatever Angular uses by default) – mpen Jan 23 '13 at 17:27
  • 1
    Removed second approach. It was way too buggy. – SunnyShah Jan 23 '13 at 17:59
  • first fiddle was not latest, Fixed that too. – SunnyShah Jan 23 '13 at 18:05
  • 1
    You can use on-change directive to do further improvements. http://stackoverflow.com/questions/14477904/how-to-create-on-change-directive-for-angularjs/14478536#14478536 – SunnyShah Jan 23 '13 at 18:12
  • Well, I've almost got it how I want but there's a couple more kinks I need to work out. I think I'll open a new question. – mpen Jan 24 '13 at 04:31
  • 7
    @SunnyShah - great stuff, really helpful, thanks very much. I was wondering why you have `var dateFormat = attrs.moMediumDate;` and not `var dateFormat = attrs.moDateInput;` moMediumDate doesn't seem to be set anywhere so if the inputs aren't created dynamically an initial format is never selected. – toxaq Apr 23 '13 at 21:40
  • 1
    Updated CDN links in plunk: [link]http://plnkr.co/edit/fc5vW0?p=preview. @SunnyShah - very nice example! ty – null Aug 14 '13 at 10:05
  • Deprecation warning: moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info. – Northstrider Jun 30 '15 at 03:11
  • 2
    This example is good and thorough. It can be simplified: http://plnkr.co/edit/KpLkJZd82cHzxZe3pacc?p=preview However I ended up passing a string to the model, much simpler :) – Aximili Sep 18 '15 at 01:10
38

Here is very handy directive angular-datetime. You can use it like this:

<input type="text" datetime="yyyy-MM-dd HH:mm:ss" ng-model="myDate">

It also add mask to your input and perform validation.

  • 1
    I used this directive as well, thanks. If you are using it with the jQuery UI Datepicker, you must make sure the format specified in the `datetime=""` attribute value matches the format specified in the Datepicker's `dateFormat` option. – tylerwal Nov 06 '15 at 18:39
  • 1
    I wonder how it's possible that there is a perfect solution right here with only few up votes. So much for dates, and you can solve them right here, right now ! Thanks dude. – C0ZEN May 02 '16 at 13:36
  • 2
    I'm sure more people would vote this up, if the documentation was improved to make it easier to include in an existing angular app. – Joel Hansen Jul 13 '16 at 22:00
8

I've created a simple directive to enable standard input[type="date"] form elements to work correctly with AngularJS ~1.2.16.

Look here: https://github.com/betsol/angular-input-date

And here's the demo: http://jsfiddle.net/F2LcY/1/

Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202
7

Since you have used datepicker as a class I'm assuming you are using a Jquery datepicker or something similar.

There is a way to do what you are intending without using moment.js at all, purely using just datepicker and angularjs directives.

I've given a example here in this Fiddle

Excerpts from the fiddle here:

  1. Datepicker has a different format and angularjs format is different, need to find the appropriate match so that date is preselected in the control and is also populated in the input field while the ng-model is bound. The below format is equivalent to 'mediumDate' format of AngularJS.

    $(element).find(".datepicker")
              .datepicker({
                 dateFormat: 'M d, yy'
              }); 
    
  2. The date input directive needs to have an interim string variable to represent the human readable form of date.

  3. Refreshing across different sections of page should happen via events, like $broadcast and $on.

  4. Using filter to represent date in human readable form is possible in ng-model as well but with a temporary model variable.

    $scope.dateValString = $filter('date')($scope.dateVal, 'mediumDate');
    
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Praveenram Balachandar
  • 1,587
  • 1
  • 13
  • 16
7

I'm using jquery datepicker to select date. My directive read date and convert it to json date format (in milliseconds) store in ng-model data while display formatted date.and reverse if ng-model have json date (in millisecond) my formatter display in my format as jquery datepicker.

Html Code:

<input type="text" jqdatepicker  ng-model="course.launchDate" required readonly />

Angular Directive:

myModule.directive('jqdatepicker', function ($filter) {
    return {
        restrict: 'A',
        require: 'ngModel',
         link: function (scope, element, attrs, ngModelCtrl) {
            element.datepicker({
                dateFormat: 'dd/mm/yy',
                onSelect: function (date) {   
                    var ar=date.split("/");
                    date=new Date(ar[2]+"-"+ar[1]+"-"+ar[0]);
                    ngModelCtrl.$setViewValue(date.getTime());            
                    scope.$apply();
                }
            });
            ngModelCtrl.$formatters.unshift(function(v) {
            return $filter('date')(v,'dd/MM/yyyy'); 
            });

        }
    };
});
leesei
  • 6,020
  • 2
  • 29
  • 51
Amit Bhandari
  • 91
  • 1
  • 1
  • Oh my gosh!! Thank you sooooo much!! I've literally been looking two days for something like this answer, but couldn't find one! You rock! – Michael Rentmeister Dec 22 '15 at 19:07
  • Fantastic. I have been looking for something like this for a while. I am using materializecss for my solution. If someone stubles upon this and needs to convert this directive for materialize, just swap out `element.datepicker` with `element.pickadate` – IWI Dec 29 '16 at 19:24
6

I use the following directive that makes me and most users very happy! It uses moment for parsing and formatting. It looks a little bit like the one by SunnyShah, mentioned earlier.

angular.module('app.directives')

.directive('appDatetime', function ($window) {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ngModel) {
            var moment = $window.moment;

            ngModel.$formatters.push(formatter);
            ngModel.$parsers.push(parser);

            element.on('change', function (e) {
                var element = e.target;
                element.value = formatter(ngModel.$modelValue);
            });

            function parser(value) {
                var m = moment(value);
                var valid = m.isValid();
                ngModel.$setValidity('datetime', valid);
                if (valid) return m.valueOf();
                else return value;
            }

            function formatter(value) {
                var m = moment(value);
                var valid = m.isValid();
                if (valid) return m.format("LLLL");
                else return value;

            }

        } //link
    };

}); //appDatetime

In my form i use it like this:

<label>begin: <input type="text" ng-model="doc.begin" app-datetime required /></label>
<label>end: <input type="text" ng-model="doc.end" app-datetime required /></label>

This will bind a timestamp (milliseconds since 1970) to doc.begin and doc.end.

Elmer
  • 9,147
  • 2
  • 48
  • 38
  • 1
    It's easy to overlook the fact that, inside this link function, `ngModel` is your custom identifier for the controller argument, not a direct, named reference to `ngModel`. This is a spot where the convention of naming that arg 'ctrl' or 'controller' keeps things clearer and avoids confusion. – XML Oct 22 '14 at 00:56
  • Thanks! I've been looking all day for a nice way to do this. Unfortunately, most other fixes involved switching to a datepicker written specifically for angular, and that was too much work. – Josh Mouch Dec 29 '15 at 22:41
  • I tried both yours and @SunnyShah 's answer, but his didn't seem to work. Not sure why. – Josh Mouch Dec 29 '15 at 22:42
3

In Angular2+ for anyone interested:

<input type="text" placeholder="My Date" [ngModel]="myDate | date: 'longDate'">

with type of filters in DatePipe Angular.

Quan VO
  • 1,258
  • 11
  • 19
  • Dang, I'm stuck in legacy AngularJS which I've been forced to work on, and I hate it! Wish I had the above to work with. – Jordan Aug 21 '19 at 21:56
  • This is just teasing people and tricking them into using Angular2+. Yuck! AngularJs is totally the bomb. Why would anyone want a simple solution like this. – Nebulosar Aug 26 '20 at 08:39
2

I prefer to have the server return the date without modification, and have javascript do the view massaging. My API returns "MM/DD/YYYY hh:mm:ss" from SQL Server.

Resource

angular.module('myApp').factory('myResource',
    function($resource) {
        return $resource('api/myRestEndpoint/', null,
        {
            'GET': { method: 'GET' },
            'QUERY': { method: 'GET', isArray: true },
            'POST': { method: 'POST' },
            'PUT': { method: 'PUT' },
            'DELETE': { method: 'DELETE' }
        });
    }
);

Controller

var getHttpJson = function () {
    return myResource.GET().$promise.then(
        function (response) {

            if (response.myDateExample) {
                response.myDateExample = $filter('date')(new Date(response.myDateExample), 'M/d/yyyy');
            };

            $scope.myModel= response;
        },
        function (response) {
            console.log(response.data);
        }
    );
};

myDate Validation Directive

angular.module('myApp').directive('myDate',
    function($window) {
        return {
            require: 'ngModel',
            link: function(scope, element, attrs, ngModel) {

                var moment = $window.moment;

                var acceptableFormats = ['M/D/YYYY', 'M-D-YYYY'];

                function isDate(value) {

                    var m = moment(value, acceptableFormats, true);

                    var isValid = m.isValid();

                    //console.log(value);
                    //console.log(isValid);

                    return isValid;

                };

                ngModel.$parsers.push(function(value) {

                    if (!value || value.length === 0) {
                         return value;
                    };

                    if (isDate(value)) {
                        ngModel.$setValidity('myDate', true);
                    } else {
                        ngModel.$setValidity('myDate', false);
                    }

                    return value;

                });

            }
        }
    }
);

HTML

<div class="form-group">
    <label for="myDateExample">My Date Example</label>
    <input id="myDateExample"
           name="myDateExample"
           class="form-control"
           required=""
           my-date
           maxlength="50"
           ng-model="myModel.myDateExample"
           type="text" />
    <div ng-messages="myForm.myDateExample.$error" ng-if="myForm.$submitted || myForm.myDateExample.$touched" class="errors">
        <div ng-messages-include="template/validation/messages.html"></div>
    </div>
</div>

template/validation/messages.html

<div ng-message="required">Required Field</div>
<div ng-message="number">Must be a number</div>
<div ng-message="email">Must be a valid email address</div>
<div ng-message="minlength">The data entered is too short</div>
<div ng-message="maxlength">The data entered is too long</div>
<div ng-message="myDate">Must be a valid date</div>
Northstrider
  • 1,149
  • 13
  • 14
1

Angularjs ui bootstrap you can use angularjs ui bootstrap, it provides date validation also

<input type="text"  class="form-control" 
datepicker-popup="{{format}}" ng-model="dt" is-open="opened" 
min-date="minDate" max-date="'2015-06-22'"  datepickeroptions="dateOptions"
date-disabled="disabled(date, mode)" ng-required="true"> 



in controller can specify whatever format you want to display the date as datefilter

$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];

P.JAYASRI
  • 358
  • 2
  • 18