jQuery UI Datepicker with AngularJS

Yesterday I had to implement jQuery’s Datepicker with Angularjs, at first I thought it would be straight forward, just a directive to configure the datepicker and setup the input field, but I found there is more behind it. The calendar was working fine but the model were not being updated with the selected date. So after some research I came up with the solution bellow:

app.directive('datepicker', function() {
    return {
        restrict: 'A',
        require : 'ngModel',
        link : function (scope, element, attrs, ngModelCtrl) {
            $(function(){
                element.datepicker({
                    dateFormat:'dd/mm/yy',
                    onSelect:function (date) {
                        scope.$apply(function () {
                            ngModelCtrl.$setViewValue(date);
                        });
                    }
                });
            });
        }
    }
});

The directive is restricted to an attribute and the link() function is used to setup the datepicker. The interesting part in inside the onSelected method, there I am using the ngModelController to update the model using it’s $setViewValue method.

The ngModelController

“NgModelController provides API for the ng-model directive. The controller contains services for data-binding, validation, CSS update, value formatting and parsing. It specifically does not contain any logic which deals with DOM rendering or listening to DOM events. The NgModelController is meant to be extended by other directives where, the directive provides DOM manipulation and the NgModelController provides the data-binding.” – Angular Docs

In my opinion this is the best solutions because you rely only on the ngModel directive for the two-way data bind, there is no need to use $parse or reach the model directly through the scope.

You can try it here.

EDIT: As pointed by jsanti, don’t forget to include the scripts in order:

1 – Jquery
2 – Jquery-ui
3 – angularjs.

Otherwise Angular will use JQlite intead of JQuery and the directive will break.

EDIT: As pointed by Craig, wrapp ngModelCtrl.$setViewValue(date) with scope.$apply().

 


46 Responses

  • osvaldoMay 22, 2013 at 11:44 pm

    The best answer I found, simple and useful

    Excellent work!!

    I’ll keep following u

  • bemaMay 23, 2013 at 12:55 pm

    Thanks Osvaldo, glad you liked it! ;)

  • RodneyJune 27, 2013 at 12:47 am

    Thanks for your answer, simple, clean and fast, thank you so much, finally finish my job

  • et_austinJuly 2, 2013 at 6:30 pm

    Very useful, thanks!!!

  • Tim BeanJuly 4, 2013 at 6:46 am

    would you allow we copy some content from the article? Thanks.

  • MatejJuly 5, 2013 at 12:24 pm

    Thanks for your answer:)

  • sWatJuly 10, 2013 at 3:59 pm

    Thanks for the solution. I am finding a small difficulty in the above date picker, that is its very hectic to select the old dates. How can we have a dropdown for month and year in our calender. It will be very helpful if you suggest some answers. Thank you in advance :)

  • Andrey BottoniJuly 10, 2013 at 5:37 pm

    Hi Bema ! Excellent example. Thanks for taking the time to develop it!.

    I have used it in my project without no problem (when I have only one datepicker in the form) but now I’m facing a problem because I have 2 datepickers and no matter which one of them I select, it will always show the same date for both inputs. Do you have any idea of how this can be solve?

    Thanks in advance for the help !

  • bemaJuly 10, 2013 at 6:51 pm

    Hi sWat,

    To select old dates easily you could display month and year dropdowns inside the calendar, for that you just have to set changeMonth = true and changeYear = true inside datepicker’s configuration object. Have a look at this Plunker.

    Best

  • bemaJuly 10, 2013 at 6:59 pm

    Hi Andrey,

    This is happening because both inputs are pointing to the same model, so when you change one both get updated. The fix is simple, set each input to a different model, this is done in the ng-model attribute of the input.

    Have a look at this plunker.

    Hope it helps.

  • bemaJuly 10, 2013 at 7:04 pm

    Sure Tim, the content of the blog is under Creative Commons (CC BY 3.0)

  • Andrey BottoniJuly 11, 2013 at 11:52 am

    Actually it helps! hahaha it was really easy ! Thanks man ! I will keep following your blog for sure !

    Thanks !

  • SergeyJuly 27, 2013 at 7:16 pm

    Thanks, you’ve made my day!

  • Dave AckermanAugust 20, 2013 at 11:49 pm

    How can I send a different date format to the model?

  • bemaAugust 21, 2013 at 3:28 pm

    Hi Dave,

    You could change the format inside the onSelect method and update the model using ngModelCtrl.$setViewValue(newDate). Here is a plunker.

  • jsantiAugust 23, 2013 at 4:16 am

    Very Helpful!. Something important to note when using this directive —> include the scripts in order:
    1 – Jquery
    2 – Jquery-ui
    3 – angularjs.

    thank’s! code and fun!

  • bemaAugust 28, 2013 at 10:54 pm

    Good point jsanti, otherwise angular will use JQlite instead of Jquery and the directive will break. I’ve included your comment in the post.
    Thanks and good coding ;)

  • Adrian HedleyAugust 30, 2013 at 1:19 pm

    i’m an angular newbie but loving it so far.

    your example is a bit advanced for me but i managed to get to work on my first angular based web app. thanks great work.

    I was wondering if i was possible to integrate it with the angular ui at http://angular-ui.github.io/

  • bemaAugust 30, 2013 at 2:59 pm

    Hi Adrian,
    If you are using angular-ui maybe it’d be better if you use bootstrap-ui datepicker, this way you won’t depend on JQuery for the datepicker. I really like angular-ui, it is maturing fast and I think it is a good choice for angular apps, but the solution above is a good one if you have an app that already depends on JQuery-ui and you don’t want to include another library.

  • Alexandra VargasSeptember 26, 2013 at 6:51 pm

    The best solution, thanks great work. :D

  • bemaSeptember 26, 2013 at 7:26 pm

    Glad to help ;)

  • safiyuSeptember 27, 2013 at 2:36 pm

    Sir, I am getting an error when the site loads this directive.
    element.datepicker is not a function. What will be the possible mistake i would have done??

  • bemaSeptember 27, 2013 at 3:35 pm

    Hi safiyu,
    Have you included the JqueryUI script? I guess you forgot to include it or the JqueryUI you are using is a custom built that doesn’t include datepicker.
    Check this JSBin, I have commented out the JQueryUI in the head and it is getting the same error (element.datepicker is not a function).

  • Spicer MatthewsSeptember 30, 2013 at 3:54 am

    Thank You!!

    This solution has solved a big problem for me. I was going about fixing this all the wrong way. thanks for sharing.

  • SaraswatiOctober 5, 2013 at 6:32 pm

    thank you ….
    very helpful….

  • PandaOctober 22, 2013 at 11:00 pm

    Hey, awesome solution, thanks for sharing!

  • cliffOctober 29, 2013 at 4:16 pm

    nice post. thank you!
    have you tried to implement it with the icon trigger?
    http://jqueryui.com/datepicker/#icon-trigger

  • bemaOctober 30, 2013 at 1:39 pm

    Hi cliff, glad you like it. To use the icon you just have to configure the datepicker, the rest stays the same , here is a plunker

  • cliffOctober 31, 2013 at 3:37 am

    thanks again for implementing it. it works very well.

    how about if the trigger is a button, which is not part of the date picker? i exploring how to make two directives communicate with each other in Angularjs. sorry for being a pain :)

  • bemaOctober 31, 2013 at 8:43 pm

    Hi cliff, there are several ways to make directives communicate to each other, usually I prefer services, but you could also use the scope or the directive’s controller depending on your case.
    Have a look at this plunker, I’ve coded two directives, one with an isolated scope and a controller that is responsible for the datepicker, and the other that requires the datepicker’s controller and uses it’s toggle function to show/hide the calendar. Once a date is selected the datepicker directive will broadcast the selected value to it’s child scopes.

    Another thing is that the datepicker is being attached to a div element instead of an input.
    I think this is more or less what you asked, init? ;)
    Hope it is useful.
    Best

  • LanierNovember 14, 2013 at 10:51 pm

    Hey, element doesn’t have a reference to datepicker. Yes I have a reference to jqueryUI you can see that I can access the method by grabbing the element from the DOM, but accessing by element doesn’t seem to work. HELP?

    In the console:
    element.datepicker()
    TypeError: Object [object Object] has no method ‘datepicker’
    $(‘#docExpDate’).datepicker()
    [

    ]
    element
    [

    ]

  • CraigNovember 15, 2013 at 11:43 pm

    Thanks for the post this is the most helpful example of writing a directive to wrap the jquery ui datepicker I’ve found. Can you help clarify a few thing for me. I actually don’t want a datepicker but a calendar that always shows which should be easy since the jquery ui datepicker allows for this by simply changing the to a with the datepicker directive which I did in this fork of your example.
    http://plnkr.co/edit/NI2HZ8H3FMn6fnHir2Rj?p=preview
    But the initialization of the date $scope.date = ’19/03/2013′; doesn’t select the correct date in the calendar.

    I added a jquery ui stylesheet to your example so the selected item can be seen.

    Several examples including this one from an AngularJS book by O’Reilly
    https://github.com/shyamseshadri/angularjs-book/blob/master/chapter8/datepicker/datepicker.js
    not only call setViewValue but also call scope.$render() in the link function to sync the other direction model to view but this seems to break more stuff in your example than help so I commented it in my example but I guess I’m also unclear on why you didn’t need to call $render in your example. Thanks for any insight in advance.

  • CraigNovember 15, 2013 at 11:53 pm

    Great post again. Forgot to mention. Instead of this:
    ngModelCtrl.$setViewValue(date);
    scope.apply();

    Best practice is to call it like this:
    scope.$apply(function(){
    ngModelCtrl.$setViewValue(date);
    });

    The best rationale I’ve read about why is at the bottom of this article:
    http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

  • bemaNovember 18, 2013 at 3:17 pm

    Hi Lanier, sorry about the delay… being busy… to make it easier please set a fiddle or jsbin with your code, otherwise I can just speculate about the problem :)

  • bemaNovember 18, 2013 at 3:57 pm

    Hi Craig, I see the problem, when the datepicker is visible it won’t update when the date model changes (I’ve simulated it with a setTimeout).
    In this case I think the easiest way is to watch the model and apply any changes using the date picker’s setDate.
    ngModelCtrl.$render won’t work because the calendar is controlled by JQuery, meaning it is “outside” Angular.
    Here is a plunker

  • bemaNovember 18, 2013 at 3:59 pm

    Good point, I’ve updated the post ;).
    Best

  • yakirDecember 4, 2013 at 6:59 pm

    Very nice article. Thank you!
    I’m a beginner in angularjs. say that I want to be able to selecet a range of date, like in this example:
    with the user not able to select toDate < fromDate etc… how would you implement that in angular?

  • bemaDecember 4, 2013 at 10:25 pm

    Hi yakir,
    I think the simplest way is to use the same directive to implement one datepicker for each model (fromDate and toDate), the directive could check if the element has a minDate parameter (populated with fromDate’s value) and we use it to set it’s minDate option, this is done inside datepicker’s beforeShow function. You could use the same approach to set maxDate if you needed. Here is a plunker
    Hope it helps
    best

  • wefDecember 10, 2013 at 6:11 pm

    Works great…thanks, I’ve been able to use this to make other directives as well.

    One question though

    how do you use the attrs to pass dynamic values, such as maxDate, et.al.?

    thanks

  • NitishDecember 16, 2013 at 12:48 pm

    Hi

    Your solution works great for all browsers except ie 8 .
    Is there a fix for ie 8 it doesnt seem to load angular app .
    I tried ur demo link also but same issue

  • valenteJanuary 19, 2014 at 8:22 pm

    Hi Nitish,

    Luckily I don’t support i.e. 8 anymore, neither personally nor at work, I don’t even have it installed so unfortunately I won’t be able to help you. You could take that question to stackoverflow though.
    Best

  • valenteJanuary 19, 2014 at 8:41 pm

    Hi wef,

    In a nutshell, Angular normalises all attributes you set in the directives element and makes them available in the attrs object. this means that in case of dynamic values, for every digest cycle the expression will be run and its result bound to the attrs.

  • platusAugust 3, 2014 at 1:19 pm

    Writing "scope.date = date" gives the same result. : : link : function (scope, element, attrs, ngModelCtrl) { $(function(){ element.datepicker({ dateFormat:'dd/mm/yy', onSelect:function (date) { scope.date = date; // ngModelCtrl.$setViewValue(date); <-- NO NEED? scope.$apply(); : : Can anyone explain why $setViewValue is needed?

  • RyanSeptember 5, 2014 at 6:51 pm

    Is there any way to get this to work within ng-repeat elements?

  • BemaSeptember 9, 2014 at 12:57 pm

    Hi Platus, $setViewValue is preferred because this way the directive doesn’t have to know the name of the model it has to update, that is why we use ngModelCtrl, so if you have another input that binds to another model, lets say, dateOfBirth, the directive would work without need to update scope.dateOfBirth.

  • BemaSeptember 9, 2014 at 1:13 pm

    Hey Ryan, I've set a plunker with ngRepeat, the trick is to bind each input to a different model http://plnkr.co/edit/IbxI6VQHGcItAeqJObEq?p=preview

Leave a reply

Your email will not be published

Name is required

Field limit is 50 chars

Email is required

Email is invalid

Field limit is 50 chars

Aren't you going to comment?

Field limit is 500 chars

You may use <code> tag

Please leave this input empty

{{ error }}