angularJS:等待在指令加载之前评估模板

时间:2022-08-24 17:06:46

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.

假设我有一个指令,它必须通过ID在定义指令的元素内访问某些元素。可能出现的问题是,在评估指令时,子元素还没有。结果是,我无法通过其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 comment in the code, the id is still "{{ item.id }}". So angular didn't evaluate this part yet.

因此,我的指令应该只使用$ scope.elementId中的id绘制元素的背景颜色。 (顺便说一句。我知道我可以更轻松地处理这个简单的例子,它应该只是说明一般问题)。问题是,ng-repeat中的元素的id还没有。正如代码中的注释所指出的那样,id仍然是“{{item.id}}”。所以有角度还没有评估这部分。

Question

My obvious question is now: how can I make my directive to wait for descendent elements to be completely 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.

在我的实际应用程序中,我希望有一个指令,使我能够滚动到页面上的某些元素。我还使用分页指令来分割我想要显示的元素。由于分页,只有真正可见的元素在DOM中,因此隐形元素已经在我的控制器中被过滤掉了。

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. 跳转到正确的页面
  3. scroll to the corrent element
  4. 滚动到corrent元素

When I jump to the page, I basically have the situation, I described above. I have a complete 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.

当我跳到页面时,我基本上都有这种情况,我在上面描述过。我有一个完整的新元素列表,必须由ng-repeat处理。但是在那之后,我尝试告诉我的scroll-directive,它应该使用ID“xy”滚动元素,但是这个ID还没有分配。

4 个解决方案

#1


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)

使用$ timeout包装$ scope.elementId =“Id1”以通知angular调用侦听器。 (也可以使用$ scope。$ apply()完成,但这会导致另一个问题)

here is the jsfiddle link

这是jsfiddle链接

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";
    });
});

#2


2  

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:

如果最终编写了一个getElementById辅助函数,它返回一个promise并有一个内部间隔,如果元素存在与否则每100ms检查一次:

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.

它仍然不是我的首选解决方案,但它现在可以解决我的问题。但我仍然很好奇,如果有更好的方法。

#3


1  

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

根据Jim Hoskins的文章,以下代码段可以帮助您。

  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 complete article)

发布这个答案,以帮助人们节省一些时间(当然,阅读完整的文章是有帮助的)

#4


0  

You should complete 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.

这将在控制器范围内创建所有内容,然后您可以从使用此指令的视图的控制器访问它。

#1


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)

使用$ timeout包装$ scope.elementId =“Id1”以通知angular调用侦听器。 (也可以使用$ scope。$ apply()完成,但这会导致另一个问题)

here is the jsfiddle link

这是jsfiddle链接

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";
    });
});

#2


2  

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:

如果最终编写了一个getElementById辅助函数,它返回一个promise并有一个内部间隔,如果元素存在与否则每100ms检查一次:

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.

它仍然不是我的首选解决方案,但它现在可以解决我的问题。但我仍然很好奇,如果有更好的方法。

#3


1  

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

根据Jim Hoskins的文章,以下代码段可以帮助您。

  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 complete article)

发布这个答案,以帮助人们节省一些时间(当然,阅读完整的文章是有帮助的)

#4


0  

You should complete 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.

这将在控制器范围内创建所有内容,然后您可以从使用此指令的视图的控制器访问它。