9

情况

假设我有一个指令,它必须通过 ID 访问某些元素,在定义指令的元素内。可能发生的问题是,在评估指令时,子元素还没有。结果是,我无法通过它们的 ID 访问这些元素。

例子

小提琴

<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>

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

问题

我现在明显的问题是:我怎样才能让我的指令等待后代元素被完全评估?

进一步解释

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

我还有一个侧边栏,其中有指向所有元素(不仅是可见元素)的小链接。当有人点击侧边栏中的元素时,应该会发生两个事件:

  1. 跳转到正确的页面
  2. 滚动到当前元素

当我跳转到页面时,我基本上有我上面描述的情况。我有一个完整的新元素列表,必须由 ng-repeat 处理。但紧接着,我尝试告诉我的滚动指令,它应该滚动 ID 为“xy”的元素,但尚未分配此 ID。

4

4 回答 4

4

用 $timeout 包裹您的 $scope.elementId = "Id1" 以通知 Angular 调用侦听器。(这也可以使用 $scope.$apply() 来完成,但这会导致另一个问题)

这是jsfiddle链接

代码是 -

    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";
    });
});
于 2013-11-07T17:10:47.750 回答
2

如果最终编写了一个getElementById辅助函数,它返回一个承诺并有一个内部间隔,它每 100 毫秒检查一次元素是否存在:

更新的小提琴

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

在我给定的示例中,我会这样使用它:

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

它仍然不是我的首选解决方案,但它现在有效并解决了我的问题。但我仍然很好奇,如果有更好的方法来解决这个问题。

于 2013-11-08T13:49:57.613 回答
1

根据 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))
  });

发布此答案以帮助人们节省一些时间(当然阅读完整的文章会有所帮助)

于 2015-11-27T08:56:49.790 回答
0

您应该完成包含controller选项的指令。

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

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

于 2013-11-07T15:11:13.710 回答