最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - angularJS: wait for template to be evaluated before directive loads - Stack Overflow

programmeradmin3浏览0评论

The Situation

Lets say I have a directive, that has to access certain elements via ID, inside the element on which the directive is defined. The problem, that can occur, is that by the time the directive is evaluated, the child-elements are not yet. The result is, that I'm not able to access those elements by their ID.

Example

FIDDLE

<div ng-controller="MyCtrl">
  <div color="elementId">
      <div ng-repeat="item in items" id="{{ item.id }}">
          {{ item.name }}
      </div>
  </div>
</div>

<script>
    var myApp = angular.module('myApp',[]);

    myApp.directive("color", function () {
        return {
            restrict: "A",   
            link: function (scope, element, attributes) {

                var name = attributes.color,
                    el = element[0];

                scope.$watch(name, function () {
                    var id = scope[name];
                    console.log(id); //id1
                    console.log(element.children().eq(0).attr("id")); //{{ item.id }}
                    element.find("#"+id).css("background-color","red");
                });
            }        
        };
    });

    function MyCtrl($scope) {
        $scope.items = [
            { id:"id1", name:"item1" },
            { id:"id2", name:"item2" }
        ];

        $scope.elementId="id1";
    }

</script>

So my directive should just paint the background-color of the element with the id in $scope.elementId. (Btw. I know I can handle this simple example much easier, it should just illustrate the general issue). The problem is, that the ids of the elements inside ng-repeat are not there yet. As pointed out in the ment in the code, the id is still "{{ item.id }}". So angular didn't evaluate this part yet.

Question

My obvious question is now: how can I make my directive to wait for descendent elements to be pletely evaluated?

Further Explaination

In my real application I want to have a directive, that enables me to scroll to a certain elements on the page. I also use a pagination directive to split up the elements I want to show. Because of the pagination, only the elements that are really visible, are in the DOM, so the invisible elements are already filtered out in my controller.

I also have a sidebar, where are small links to ALL the elements (not only the visible ones). When someone clicks on an element in the sidebar, two events should occur:

  1. jump to the correct page
  2. scroll to the corrent element

When I jump to the page, I basically have the situation, I described above. I have a plete new list of elements, that have to be processed by ng-repeat. But directly after that, I try to tell my scroll-directive, that it should scroll the element with the ID "xy", but this ID is not assigned yet.

The Situation

Lets say I have a directive, that has to access certain elements via ID, inside the element on which the directive is defined. The problem, that can occur, is that by the time the directive is evaluated, the child-elements are not yet. The result is, that I'm not able to access those elements by their ID.

Example

FIDDLE

<div ng-controller="MyCtrl">
  <div color="elementId">
      <div ng-repeat="item in items" id="{{ item.id }}">
          {{ item.name }}
      </div>
  </div>
</div>

<script>
    var myApp = angular.module('myApp',[]);

    myApp.directive("color", function () {
        return {
            restrict: "A",   
            link: function (scope, element, attributes) {

                var name = attributes.color,
                    el = element[0];

                scope.$watch(name, function () {
                    var id = scope[name];
                    console.log(id); //id1
                    console.log(element.children().eq(0).attr("id")); //{{ item.id }}
                    element.find("#"+id).css("background-color","red");
                });
            }        
        };
    });

    function MyCtrl($scope) {
        $scope.items = [
            { id:"id1", name:"item1" },
            { id:"id2", name:"item2" }
        ];

        $scope.elementId="id1";
    }

</script>

So my directive should just paint the background-color of the element with the id in $scope.elementId. (Btw. I know I can handle this simple example much easier, it should just illustrate the general issue). The problem is, that the ids of the elements inside ng-repeat are not there yet. As pointed out in the ment in the code, the id is still "{{ item.id }}". So angular didn't evaluate this part yet.

Question

My obvious question is now: how can I make my directive to wait for descendent elements to be pletely evaluated?

Further Explaination

In my real application I want to have a directive, that enables me to scroll to a certain elements on the page. I also use a pagination directive to split up the elements I want to show. Because of the pagination, only the elements that are really visible, are in the DOM, so the invisible elements are already filtered out in my controller.

I also have a sidebar, where are small links to ALL the elements (not only the visible ones). When someone clicks on an element in the sidebar, two events should occur:

  1. jump to the correct page
  2. scroll to the corrent element

When I jump to the page, I basically have the situation, I described above. I have a plete new list of elements, that have to be processed by ng-repeat. But directly after that, I try to tell my scroll-directive, that it should scroll the element with the ID "xy", but this ID is not assigned yet.

Share Improve this question edited Nov 8, 2013 at 13:51 basilikum asked Nov 7, 2013 at 15:04 basilikumbasilikum 10.5k5 gold badges47 silver badges58 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 4

Wrap your $scope.elementId = "Id1" with $timeout to notify angular to call listeners. (this can alternatively be done with $scope.$apply(), but it's causing another issue here)

here is the jsfiddle link

Code is -

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

    myApp.directive("color", ['$timeout',  function ($timeout) {
        return {
            restrict: "A",   
            link: function (scope, element, attributes) {
                console.log(element)
                var name = attributes.color,
                    el = element[0];

                 scope.$watch(name, function () {
                     var id = scope[name];
                     console.log(id); //id1
                     console.log(element.find("#"+id)); //{{ item.id }}
                     element.find("#"+id).css("background-color","red");
                 });
            }        
        };
    }]);

myApp.controller("MyCtrl", function($scope, $timeout) {
    $scope.items = [
        { id:"id1", name:"item1" },
        { id:"id2", name:"item2" }
    ];

    $timeout(function() {
        $scope.elementId="id1";
    });
});

If finally ended up writing a getElementById helper function, that returns a promise and has an internal interval, that check every 100ms if the element is present or not:

updated Fiddle

function getElementById(elementId) {
    var deferred = $q.defer(),
        intervalKey,
        counter = 0, 
        maxIterations = 50;

    intervalKey = setInterval(function () {
        var element = document.getElementById(elementId);
        if (element) {
            deferred.resolve(element);
            clearInterval(intervalKey);
        } else if (counter >= maxIterations) {
            deferred.reject("no element found");
            clearInterval(intervalKey);
        }
        counter++;
    }, 100);

    return deferred.promise;
}

In my given example, I would use it like this:

getElementById(id).then(function (element) {
    $(element).css("background-color","red");
}, function (message) {
    console.log(message);
});

It's still not my preferred solution, but it works and solves my problem for now. But I'm still curious, if there is any better approach to this.

As per Jim Hoskins article, the following snippet should help you.

  scope.$watch(name, function () {
    setTimeout(function () {
      scope.$apply(function () {
        var id = scope[name];
        console.log(id); //id1
        console.log(element.find("#"+id)); //{{ item.id }}
        element.find("#"+id).css("background-color","red");
      }  
    }, 200))
  });

Posting this answer to help people save some time(of course it's helpful to read the plete article)

You should plete the directive including a controller option.

controller: function ($scope){
     $scope.items = [
        { id:"id1", name:"item1" },
        { id:"id2", name:"item2" }
    ];
}

This will create everything in the controller scope, and then you can access it from the controller of the view that uses this directive.

发布评论

评论列表(0)

  1. 暂无评论