3

In my directive, I need to select out certain DOM elements, some of which are generated dynamically in an ng-repeat loop. If I do it in a straightforward way, I will only get the static elements. However, if I delay the selection by, say, 500ms, I will get all elements, which is what I want.

Although this works, it is not an ideal solution, and certainly doesn't seem like best practise. On the one hand, you'd like to keep the timeout as short as possible, but on the other hand, you want to be sure that the DOM is ready before selecting.

Is there an event which fires when all dynamic DOM is ready? What is the recommended way to select dynamically generated elements from an AngularJS directive?

EXAMPLE:

HTML:

<div data-my-directive>
    <div class="modal-body">                        
        <label data-localize>type:</label>&nbsp;
        <select class="form-control" ng-model="assetFilter.appCode" ng-change="loadassets(assetFilter.appCode)" ng-options="type.code as type.name for type in types"></select>

            <table class="table table-default" ng-show="hasLoaded">
                <tbody ng-repeat="asset in assets | filter:assetFilter | orderBy:'assetKey':false">
                <tr>
                    <td>
                        <div class="container-fluid">
                            <div class="row vert-align">
                                <div class="col-sm-4">
                                    {{asset.assetKey}}
                                </div>
                                <div class="col-sm-8" style="height:100%">
                                    <input ng-hide="asset.assetKey.length >= 80" type="text" class="form-control" ng-model="asset.assetValue" ng-change="asset.isModified=true">
                                    <textarea ng-show="asset.assetKey.length > 80" class="form-control" ng-model="asset.assetValue" ng-change="asset.isModified=true"></textarea>
                                </div>
                            </div>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>

    </div>
    <div class="modal-footer">
        <button class="btn btn-primary" ng-click="save(saveassets, $event)" ng-disabled="!(assets | anyModified)" data-localize>Save</button>
        <button class="btn btn-warning" ng-click="close($event)" data-localize>Close</button>
    </div>
</div>

Directive:

myApp.directive('myDirective', function ($timeout) {
    return {
        restrict: 'A', //attribute only
        link: function (scope, elem, attr, ctrl) {    
            var context = elem[0]; 
            var availableFormElements = 'input:not([disabled]):not([class*=ng-hide]),' +
                'select:not([disabled]):not([class*=ng-hide]), textarea:not([disabled]):not([class*=ng-hide]),' +
                'button:not([disabled]):not([class*=ng-hide]),' +
                '*[class*=btn]:not([disabled]):not([class*=ng-hide])';

            var allFormElements = context.querySelectorAll(availableFormElements);
            // Will only get static elements, nothing from ng-repeat loop

            $timeout(function () {
                allFormElements = context.querySelectorAll(availableFormElements);
                // Will include all elements, also from ng-repeat loop
            }, 500);     

            // Code to manipulate selected form elements   

    };
});
Per Quested Aronsson
  • 11,380
  • 8
  • 54
  • 76
  • 2
    I think another question might be why selecting these elements? Doing it in a more angular(i) way would be binding these classes to view model which will deal with the whole async issue. – Tomer Nov 01 '14 at 16:11
  • As @Tomer said, this is completely opposite of how Angular works... Why don't you put all that directive content inside the directive template and thus have it available all the time? – Shomz Nov 01 '14 at 18:01
  • 1
    All in all it seems the angular way take a look at http://stackoverflow.com/questions/17643681/angularjs-linking-to-elements-in-a-directive-that-uses-ng-repeat but this http://stackoverflow.com/questions/13471129/angularjs-ng-repeat-finish-event may be a better approach – Whisher Nov 01 '14 at 18:33
  • @Shomz: I want to keep the directive generic and reusable, not bound to any particular view. – Per Quested Aronsson Nov 02 '14 at 09:17

1 Answers1

1

This is a simple example how you could work it out. Imo the only drawback to this solution is you can't use an isolate scope.

html

<div data-ng-controller="MainController">
    <div outer-directive>
        <ul>
            <li ng-repeat="asset in assets" inner-directive>
                      {{asset}}
                      <input type="text" class="form-control">
            </li>
        </ul>
    </div>
</div>

js

var app = angular.module('myApp', []);

app.controller('MainController',function($scope) {
    $scope.assets = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; 
});

app.directive('outerDirective', function() {
  return {
    restrict: 'A',
    controller: function($scope) {

    }
  };
});
app.directive('innerDirective', function() {
  return {
    restrict: 'A',
    require: '^outerDirective',
    link: function(scope, elem, attrs,ctrl) {
        var context = elem[0]; 
        if (scope.$last){
            var availableFormElements = 'input,textarea';
            var allFormElements = context.querySelectorAll(availableFormElements);
            console.log(allFormElements);
        }
    }
  };
});

or better

.directive('myParent', function ($timeout) {
        return {
            restrict: 'A', //attribute only
            controller: function ($scope, $element) { 
                this.isDone = function(){
                    var context = $element[0]; 
                    var availableFormElements = 'input,textarea';
                    var allFormElements = context.querySelectorAll(availableFormElements);
                    console.log(allFormElements);
                }
            }
        };
    })
    .directive('myChild', function ($timeout) {
        return {
            require:'^myParent',
            restrict: 'A', //attribute only
            link: function (scope, elem, attr, ctrl) {    

                if (scope.$last){
                    ctrl.isDone();
                }
            }
        };
    })

BTW Don't touch the dom in the controller :)

Whisher
  • 31,320
  • 32
  • 120
  • 201