Search code examples
javascriptangularjsangularjs-directiveangularjs-scope

Multiple custom directives on 1 page with callbacks


I have created a custom directive (with some help from SO). It's a full page date picker which uses ngModel to set the date when the user selects a date. At the same time it calls a callback function which is set when the directive is added to the page. The problem is that if I put 2 date pickers on the page then it always tries to call the callback from the second one.

Here is the HTML...

<fp-date-picker ng-model="dateOne" on-select="dateSelectedOne()"></fp-date-picker>

<fp-date-picker ng-model="dateTwo" on-select="dateSelectedTwo()"></fp-date-picker>

Here is my directive with most of it removed for brevity

angular.module('directives').directive('fpDatePicker', ['$parse', function ($parse) {

    return {

        restrict: 'EA',
        require: 'ngModel',
        templateUrl: '/templates/fpDatePicker.tpl.html',

        link: function($scope, $element, $attrs, ngModel){

            // A callback executed when a date is selected
            var onSelectCallback = $parse($attrs.onSelect);


            // Set current month
            $scope.selectedDate = moment();



            // Set date when one is clicked
            $scope.selectDate = function(day){

                var selectedDay = day.number;
                var selectedMonth = $scope.currentMonth.format('MM');
                var selectedYear = $scope.currentMonth.format('YYYY');

                $scope.selectedDate = moment(selectedYear + '-' + selectedMonth + '-' + selectedDay, 'YYYY-MM-DD');

                // Update the model
                ngModel.$setViewValue($scope.selectedDate);


                // Call the on-select callback
                onSelectCallback($scope);

            };


        }


    };

}]);

And just in case it's needed, here is my directive template....

<div class="fpDatePicker-controls">
    <a href="#" ng-click="previousMonth()">&lt;</a>
    <a href="#" ng-click="nextMonth()">&gt;</a>
</div>
<div class="fpDatePicker-month">
    <a class="fpDatePicker-day" 
        ng-repeat="day in currentMonth.days" 
        ng-click="selectDate(day)"
    >
        {{day.number}}
    </a>
</div>

I thought that the problem of it always using the callback from the last instance of the directive on the page might be something to do with variables in the link function being global to all instances of that directive, or $attrs not being the attrs of the current instance of the directive, but I used console.log() to show the $attrs and it was different in each case.

I also wondered if it had anything to do with making the scope isolated, but although the callback functions sit on the scope, the expression in the attributes is not part of the scope, it's an attribute being passed in.

Very confused, any help would really be appreciated.

UPDATE: Created a Plunkr

http://plnkr.co/edit/nU27Wjpu3UYCR9q6DnLw?p=preview


Solution

  • Your problem comes from the fact that your directive does not rely on isolated scope. Both directive instances thus share the same scope. When the second directive is created, it overrides all scope variables.

    You can read more about isolated scope in the doc: https://docs.angularjs.org/guide/directive.

    Here is a plunkr showing how to use isolated scopes: http://plnkr.co/edit/U64sbukqfFW0NQmvIC1a

    I have updated your markup to make it compatible with the controller as syntax:

    <body ng-controller="MainCtrl as main">
        {{main.dateOne}}
        <fp-date-picker ng-model="main.dateOne" on-select="main.dateSelectedOne()"></fp-date-picker>
    
        <br />
        <br />
        <br />
    
        {{main.dateTwo}}
        <fp-date-picker ng-model="main.dateTwo" on-select="main.dateSelectedTwo()"></fp-date-picker>
    
    </body>
    

    The main change in your directive definition, is the use of the scope attribute in the directive definition object to have an isolated scope:

    .directive('fpDatePicker', function() {
    
        return {
          scope: {
            select: '&onSelect'
          },
    
          restrict: 'EA',
          require: 'ngModel',
          templateUrl: 'datepicker.html',
    
          link: function($scope, $element, $attrs, ngModel) { ... }
         };
      })
    

    Please note your code still has issues with date selection. Clicking on a day will select the date of the previous day. I did not fix that. Let me knopw if you need help