Search code examples
javascriptangularjsangularjs-directiveangularjs-scopeangularjs-digest

How to re-register a dynamically loaded angular templates' directive


I have a directive which loads a template with a bunch on input fields. One of which is the jQuery/Bootstrap datepicker.

<my-directive-buttons></my-directive-buttons>

When a user selects/clicks on the datepicker field, the calendar is displayed. I have also attached an ng-click to the input field:

<div class='col-sm-6'>
    <div class="form-group">
        <div class='input-group datepick'>
            <input type='text' class="form-control" ng-click="addCalendarFooter()"/>
            <span class="input-group-addon">
                <span class="glyphicon glyphicon-calendar"></span>
            </span>
        </div>
    </div>
</div>

On click, the calender is displayed and $scope.addCalendarFooter is called:

app.directive('myDrectiveButtons', function($compile) {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {

        },
        templateUrl: 'controls/input-fields.html',
        link: function(scope, elem, attrs) {

        },
        controller: function($scope) {


            $scope.addCalendarFooter = function() {
                $('#datepicker').append($('<div></div>').load('myDir/calendar/customFooter.html'));
            }

        }
    }
});

I am successful in appending the contents of customFooter.html to the calendar, however, within customFooter.html are further ng-clicks, which when pressed, are not being called. E.g

customFooter.html

<div>
    <button ng-click="controlClick()">Click Me</button>
</div>

Yet, if i move this button out of customFooter.html and in to input-field.html, to test the button logic is correct, the click is called.

I have tried $scope.$apply and $scope.digest after the .append, however i get a 'digest already in progress error'

UPDATE:

Based on comments, below, have tried to remove jQuery and use an 'angular way'

$scope.addCalendarFooter = function() {

    var html = "<div ng-include src=\"myDir/calendar/customFooter.html\"><\div>";


    var myEl = angular.element(document.getElementsByClassName('datepicker');

    myEl.html(html);

     $compile(myEl)($scope)

}

The above inserts my .html template via the ng-include however is it replacing the contents of the datepicker rather than inserting at the bottom of it. Tried .append but that didn't worth either.


Solution

  • Quoting from your UPDATE:

    The above inserts my .html template via the ng-include however is it replacing the contents of the datepicker rather than inserting at the bottom of it. Tried .append but that didn't worth either.

    The aforementioned issue is due to using .html() method which inserts HTML to a DOM node and overwrites any existing content of the selected node:

     var html = "<div ng-include src=\"myDir/calendar/customFooter.html\"><\div>";
     var myEl = angular.element(document.getElementsByClassName('datepicker');  
     myEl.html(html); // <== here
     $compile(myEl)($scope) 
    

    What you are doing with the above logic is that you first select the .datePicker element, then you replace its inner HTML with the .html() method.

    As a workaround, you could have used .append() method instead.

    NOTE: angular.element() is Angular's wrapper for an element just as in jQuery you had $(). Therefore, using document.getElementByClassName() method is redundant in your case.


    Although the above workaround might solve your problem, but it is better to stick to a cleaner and concise approach which AngularJS may offer.

    Angularesque Approach

    You don't need to load a template partial by programmatically adding/appending the template in a controller function - at least in Angular's way. This way you might end up not binding the angular directive(s) within the dynamically added HTML correctly with a scope.

    Instead, just include the partial within the original directive template (with ng-include) and use ngIf or ngShow to display it when the button is clicked.

    Therefore, assuming that you've the footer template (customFooter.html) in the original directive template, you can achieve the expected result as in the following:

    Directive Template

    <div class='col-sm-6'>
        <div class="form-group">
            <div class='input-group datepick'>
                <input type='text' class="form-control" ng-click="addCalendarFooter()"/>
                <span class="input-group-addon">
                    <span class="glyphicon glyphicon-calendar"></span>
                </span>
            </div>
        </div>
    </div>
    <div ng-include="myDir/calendar/customFooter.html" ng-if="isCalenderFooterAdded"></div>
    

    Directive Controller

    controller: function($scope) {
        $scope.addCalendarFooter = function() {
             $scope.isCalendarFooterAdded = true;
             // or use this if you want to toggle it
             // $scope.isCalendarFooterAdded = !$scope.isCalendarFooterAdded ? true: false;
        }
    }
    

    Plunkr mimicking a similar situation.