【问题标题】:AngularJS ng-repeat applied multiple times in $compiled directiveAngularJS ng-repeat 在 $compiled 指令中多次应用
【发布时间】:2026-01-07 06:50:01
【问题描述】:

我编写了一个指令,可以为元素动态创建弹出框:

app.directive('popover', function($compile, $timeout){
    return {    
        link: function(scope, element, attrs) {

            $timeout(function() {

                // grab template
                var tpl = $(element).find('.popover-template')

                // grab popover parts of template
                var template = {
                    //$compile( $(element).siblings(".pop-content").contents() )(scope)
                    title: tpl.find('.template-title').contents(),
                    content: tpl.find('.template-content').contents()
                };

                // render template with angular
                var content = $compile(template.content)(scope);
                var title = $compile(template.title)(scope); 

                $(element).popover({
                    html: true,
                    placement: "right",
                    content: content,
                    title: title
                });

                scope.$digest()
            });

        }

    };
});

在应用程序中它看起来像这样:

<span popover>Click me</span>
<div ng-hide="true" class="popover-template">
    <div class="template-title">
        <strong>{{ x.name }} and {{ y.name }}</strong>
    </div>

    <div class="template-content">
        <div>
            <pre>f in [1,2,3]</pre>
            <div ng-repeat="f in [1,2,3]">
                item {{ f }}, index {{ $index }}
            </div>
        </div>
    </div>

</div>

弹出框已创建并显示。标题也可以正常工作。但是,ng-repeat 在任何迭代中都会应用多次:

如您所见,应该只包含 3 个元素的迭代实际上包含 3*3 个元素。该指令为恰好 3 个元素创建弹出框,所以我想这就是我的错误所在。如何确保在每个弹出框内,ng-repeat 只被调用一次?

【问题讨论】:

  • 尝试将代码放在编译块中
  • @Whisher 你能详细说明一下吗?我想将弹出框内容保留在主模板中。
  • 在指令中而不是将代码放入链接块中尝试将其放入编译块中,以便代码编译一次。我快速查看您的代码,所以......我可以错误^^顺便说一句我不明白的另一件事是你为什么使用 $digest 不需要更新范围
  • @Whisher 但是我可以访问编译函数中的指令元素吗?

标签: javascript angularjs angularjs-directive angularjs-ng-repeat ng-repeat


【解决方案1】:

问题

由于在您引导 Angular 应用程序时(在页面加载时)popover-template 元素已经在文档中,因此它已经被编译过一次。 ng-repeat 元素被 3 个新元素替换:

<!-- original -->
<div ng-repeat="f in [1,2,3]">item {{ f }}, index {{ $index }}</div>

<!-- replaced -->
<div ng-repeat="f in [1,2,3]">item 1, index 0</div>
<div ng-repeat="f in [1,2,3]">item 2, index 1</div>
<div ng-repeat="f in [1,2,3]">item 3, index 2</div>

当您在链接函数中再次编译时,会触发 3 个 ng-repeats 中的每一个,生成 3 个相同的副本,总共 9 个。

解决办法

将弹出框模板保存在单独的文件中,这样它就不会在页面加载时编译。然后您可以使用$templateCache 服务加载它。

一般来说,只要确保您没有多次编译 HTML。

【讨论】:

    【解决方案2】:

    使用 $http 或 templateCache 加载模板,而不是使用已编译的 html 作为弹出框模板。

    HTML:

    <span popover>Click me</span>
    <script type="text/ng-template" id="popover.html">
      <div class="popover-template">
        <div class="template-title">
          <strong>{{ x.name }} and {{ y.name }}</strong>
        </div>
        <div class="template-content">
          <div>
            <pre>f in [1,2,3] track by $index</pre>
            <div ng-repeat="f in [1,2,3]">
              item {{ f }}, index {{ $index }}
            </div>
          </div>
        </div>
      </div>
    </script>
    

    Javascript:

    angular.module('app',[]).directive('popover', function($compile, $timeout, $templateCache){
        return {
            link: function(scope, element, attrs) {
    
                $timeout(function() {
                    // grab the template (this is the catch)
                    // you can pass the template name as a binding if you want to be loaded dynamically
    
                    var tpl = angular.element($templateCache.get('popover.html'));
    
                    // grab popover parts of template
                    var template = {
                        title: tpl.find('.template-title').contents(),
                        content: tpl.find('.template-content').contents()
                    };
    
                    // render template with angular
                    var content = $compile(template.content)(scope);
                    var title = $compile(template.title)(scope); 
    
                    $(element).popover({
                        html: true,
                        placement: "right",
                        content: content,
                        title: title
                    });
    
                    scope.$digest()
                });
    
            }
    
        };
    });
    

    另外,我用一个工作示例制作了这个 plunker:http://embed.plnkr.co/IoIG1Y1DT8RO4tQydXnX/

    【讨论】:

    • 这个问题已经存在两年了,您发布的答案是一个指向 plunker 结果的链接,它甚至没有显示您用来解决问题的代码。即使两年后发帖人或其他人仍在寻找这个问题的答案,这也无济于事。