Altinet
  • Home
  • Blog
  • Contact





My example of Angularjs directive

On 22 May, 2014
Uncategorized
By : Bruno Samardzic
With 2 Comments
Views : 13546

One of the first things i wanted to do when i saw Angular UI directives like Datepicker is wrap them into my own. If you look at the example of Datepicker on their site, especially the one
for datepicker popup with a button, you will notice it is extremely involving and spread all over your controller.


var DatepickerDemoCtrl = function ($scope) {
  $scope.today = function() {
    $scope.dt = new Date();
  };
  $scope.today();

  $scope.clear = function () {
    $scope.dt = null;
  };

  // Disable weekend selection
  $scope.disabled = function(date, mode) {
    return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
  };

  $scope.toggleMin = function() {
    $scope.minDate = $scope.minDate ? null : new Date();
  };
  $scope.toggleMin();

  $scope.open = function($event) {
    $event.preventDefault();
    $event.stopPropagation();

    $scope.opened = true;
  };

  $scope.dateOptions = {
    formatYear: 'yy',
    startingDay: 1
  };

  $scope.initDate = new Date('2016-15-20');
  $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
  $scope.format = $scope.formats[0];
};

ALL that for just one date picker? Really? You can check out the HTML part on the UIs page, but it’s also pretty large. Granted, you can definitely delete a bunch of these functions and tags like initDate etc, but you still have a lot going on there just to hookup a datepicker with a button. So i decided to wrap those into my own directive and get rid of that code smell, and here’s what i ended up with:


(function() {
    'use strict';

    var module = angular.module('altinet.directives', []);

    module.directive('altiDatepicker', [altiDatePicker]);

    function altiDatePicker() {
        var mprefix = 'altidatePicker1234';
        var linkFunc= function(scope, elem, attrs) {
            scope[mprefix+'open'] = function ($event) {
                $event.preventDefault();
                $event.stopPropagation();

                scope[mprefix+'opened'] = true;
            };
        };
        return {
            restrict: 'AE',
            replace: true,
            scope:true,
            compile: function (element, attrs) {
                var html = '<p class="input-group">'+
                        '<input type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="'+attrs.ngModel+'" is-open="'+mprefix+'opened" ng-required="true" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />'
                        +'<span class="input-group-btn">'
                        +    '<button type="button" class="btn btn-default" ng-click="'+mprefix+'open($event)"><i class="glyphicon glyphicon-calendar"></i></button></span></p>';
                var e = angular.element(html);
                element.replaceWith(e);
                return linkFunc;
            }
        }
    }

})();

Lets’ go through it step by step:

1. first few lines is about declaring a directive, you can find a lot about that in the docs, but what you have to keep mind is that if you name your directive ‘altiDatepicker’, you will use it with a ‘data-alti-datepicker’ tag (see what they did there?).

2. every directive function has to return an object which can contain properties like “restrict” (which tells how you use the directive), “replace” (does it replace the current code),
“compile” (a function which will be executed and where you do all the magic), or “link” (a function used for hooking up).

3. Beware, if you already have “compile” defined, your “link” function WILL NOT BE EXECUTED unless you return it as a RESULT of your compile function (like in my example).

4. The compile function is the critical part, what i basically did there is took the html of a datepicker and of a button, i concatenated attribute value “ngModel”, i ran that html string through angular.element(html) function, and than replaced the directive with the resulting code. If you want, you can use the same principal and have some other attributes passed down to your custom datepicker.

5. At the end of the “compile” function, I return the linkFunc defined above. The linkFunc defines how i link this directive to scope. I use the linkFunc to add a popup opening function to the scope. Notice that i put a prefix on the functions name, the same way i do it in the html code. Why is that? No reason actually, it can also work without prefix. Actually there is one but i’ll explain later on.

6. The trickies part? The scope. It is defined scope:false by default, which means the directive uses the parent’s scope (which means all datepicker directives on the same controller (eg. form or page) share the same properties and methods). I won’t go deep into scope explanation, but what would happen when you would press any button on any picker, all popups would open.
How did i solve that problem? I defined scope:true. What does that do? It defines that this directive has it’s own child scope and it’s own method “open” attached to that scope. But since it’s a child scope, it also has access to all the parent scope’s properties which are not defined in the child, so i can easily access the model on the parrent scope. But what if I had a property “open” and i wanted to access that in directive? That’s why i have the prefix, i can now have a property “open” on the parent scope and still be able to access it. The only property on parent scope i can’t access is altidatePicker123open, but i doubt i’m going to use that:). There are other ways of dealing with this problem (like isolated scope) but I honestly couldn’t bother right now to see how that works. This was quick, dirty and it works:).

7. Finally, my usage of the directive went from:


            <p class="input-group">
              <input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="opened" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
              <span class="input-group-btn">
                <button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
              </span>
            </p>

and all that code in the controller (which would multiple with every datepicker used), to….


<input type="text" data-alti-datepicker ng-model="vm.template.testDate" />

With no extra methods on the controller whatsoever:). Now that is what i call a good encapsulation!



Previous Post Next Post 

About The Author

Bruno Samardzic


Number of Posts : 45
All Posts by : Bruno Samardzic

Comments ( 2 )

  • Mat Dec 05 , 2014 at 10:18 am / Reply

    This just saved my day!
    Just yesterday I looked at the datepicker on the UI Bootstrap page and thought exactly the same thing you did: “Why the hell is the popup datepicker so cluttered and all over my controller? For me this should be a blackbox that I don’t have to care about too much like every other input field!”
    Thanks to your example I could now solve this in a much nicer way without having to go all the way and importing a dozen other JS/CSS files just to get another datepicker implementation working.

    Thanks again!

  • Reginald Sophomore Oct 21 , 2016 at 6:19 am / Reply

    my husband was looking for Cash Receipt several days ago and learned about an online platform with lots of sample forms . If people need to fill out Cash Receipt as well , here’s a https://goo.gl/zaZRhK


Leave a Comment

Click here to cancel reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>





Recent Posts

  • Angular vs React: round 2
  • Why programming is hard
  • Orchard Workflows, undocumented gem
  • The React Redux
  • React.js invoicing CRUD application, part 2: Description

Recent Comments

  • Angshuman on Animation for starred grid rows and columns using xaml
  • Advanced Excel Training in Lucknow on Angular vs React: round 2
  • Reginald Sophomore on My example of Angularjs directive
  • Slobodan on Angular and Breeze – story so far.
  • Bruno Samardzic on Reaction to Ember-forged Durandal Angularly Knocking out my Backbone, or my JS MVC framework research conclusion.

Contact

Altinet d.o.o.

OIB: 97429091194

Čulinečka cesta 146, Zagreb

Tel: +385 (1) 2946 819

Mob: +385 (98) 210 756

IBAN: HR4323400091110406470

Latest tweets