【问题标题】:How do I do dynamic template manipulation in an AngularJS directive?如何在 AngularJS 指令中进行动态模板操作?
【发布时间】:2014-05-31 17:42:17
【问题描述】:

我才刚刚开始使用 Angular,并且正在尝试正确使用指令。我正在编写一个自定义指令,该指令接受一个对象数组并将其解析为可变数量的垂直 div。它基本上是一个网格系统,其中元素排列成堆叠的垂直列而不是行。 div 的数量随着屏幕宽度动态变化,需要 div 类动态变化,并随着页面调整大小重构 div 列中数组元素的顺序。

当我将模板的内容用作纯静态 HTML 时,一切都加载得很好。当您使用输入字段等时,过滤器会动态更改数据集。

当我使用我的指令时,初始页面加载看起来不错。但是,动态过滤被破坏了 - 它不再绑定到输入字段。更重要的是,在页面调整大小时,HTML 根本无法编译,在 DOM 中留下空白屏幕和未编译的指令标签。

我不太了解 Angular,无法解决此问题。如果我不得不猜测,听起来好像由于范围问题,$compile 页面上的某些内容没有正确绑定。

注意:我知道为模板做字符串 concat 是不好的做法,但我只想在开始弄乱嵌套指令之前让事情正常工作。

编辑:这是我的前端代码的 Github 存储库链接:https://github.com/danheidel/education-video.net/tree/master/site

HTML

<body ng-controller="channelListController">
  Creator: <input ng-model="query.creators">
  Tags: <input ng-model="query.tags">
  query: {{query}}
  <div id="channel-view">
    <channel-drawers channels="channels"></channel-drawers>
  </div>
</body>

JS

.controller('channelListController', function ($scope, $http){
  $http.get('api/v1/channels').success(function(data){
    $scope.channels = data;
  });
})
.directive('channelDrawers', function($window, $compile){
  return{
    restrict: 'E',
    replace: true,
    scope: {
      channels: '='
    },
    controller: 'channelListController',
    //templateUrl: 'drawer.html',
    link: function(scope, element, attr){
      scope.breakpoints = [
        {width: 0, columns: 1},
        {width: 510, columns: 2},
        {width: 850, columns: 3},
        {width: 1190, columns: 4},
        {width: 1530, columns: 5}
      ];

      angular.element($window).bind('resize', setWindowSize);
      setWindowSize();  //call on init

      function setWindowSize(){
        scope.windowSize = $window.innerWidth;
        console.log(scope.windowSize);
        _.forEach(scope.breakpoints, function(point){
          if(point.width <= scope.windowSize){
            scope.columns = point.columns;
          }
        });
        var tempHtml = '';
        for(var rep=0;rep<scope.columns;rep++){
          tempHtml +=
          '<div class="cabinet' + scope.columns + '">' +
            '<div class="drawer" ng-class="{' + ((rep%2 === 0)?'even: $even, odd: $odd':'even: $odd, odd:$even') + '}" ng-repeat="channel in channels | looseCreatorComparator : query.creators | looseTagComparator : query.tags | modulusFilter : ' + scope.columns + ' : ' + rep + '">' +
              '<ng-include src="\'drawer.html\'"></ng-include>' +
            '</div>' +
          '</div>';
        }
        console.log(tempHtml);
        element.html(tempHtml);
        $compile(element.contents())(scope);
      }
    }
  };
})

【问题讨论】:

  • 有一个 jsFiddle 可以连接起来进行测试吗?

标签: angularjs angularjs-directive


【解决方案1】:

当你想操作你的模板时,应该实现 $compile 函数。当您想要将模板绑定到您的范围和/或设置任何观察者时,应该实现链接功能。如果您有要插入 DOM 的动态 HTML,请问自己以下问题:

  1. 您是否正在修改模板?如果是这样,则创建一个元素模板 (angular.element(...)) 并将其附加到您的元素参数中。

  2. 您是否修改了模板(步骤 1)并且您的模板包含绑定表达式、插值表达式和/或应该从其他模板绑定的属性?如果是这样,您需要编译并链接您在步骤 1 中创建的元素。

这是一个例子:

      .directive('myDirective',function($compile) {
            restrict: 'E',
            scope: '=',
            compile: function(element, attr) {
                  // manipulating template?
                  var e = angular.element('<div ng-model="person">{{person.name}}</div>');
                  element.append(e);

                  // the following is your linking function
                  return function(scope, element, attr) {
                       // template contains binding expressions? Yes
                       $compile(e)(scope);
                  };
            }
      });

要修复代码,请尝试将模板操作移至编译函数,并在链接函数中调用 $compile(e)(scope)。

【讨论】:

  • 这似乎大部分都在工作,但我遇到了一个问题。我在每次迭代时替换模板而不是追加。但是,当我尝试使用 element.replacewith 时,它会出错,因为新元素没有有效的 parentNode。关于如何解决这个问题的任何想法?
  • 在你的编译函数中: var e = angular.element(...); element.after(e).remove(); - 我假设你有 jQuery
  • 这仍然不起作用 - after() 也在寻找一个父节点来追加元素。
  • 没有更多信息,我不知道如何提供帮助。你能发布一个 Plunker 或 Jsfiddle 吗?
  • 今天早上我花了大约 2 个小时试图让我的代码在 Plunker 和 JSFiddle 中都能正常工作,但没有成功。我在 JSFiddle 中取得了更多进展,但我一直在试图弄清楚如何在那里加载模板文件。
【解决方案2】:

首先,感谢 pixelbits 和 pfooti 的输入。它让我走上了正确的轨道。但是,我希望答案是白纸黑字,因为我们的讨论涉及技术问题,最终与实际答案无关。

基本上,这个问题的框架很差。在阅读了更多内容之后,很明显我使用 Angular 元素的方式并不是它们真正想要的。

在这种情况下,我在我的指令中进行了一堆模型操作,它确实应该发生在控制器中。我最终这样做了,并将窗口调整大小处理程序代码移动到另一个组件。

现在,我什至不需要指令来正确格式化我的数据。几个嵌套的 ng-repeats 加上 ng-class 和 ng-style 在 2 行 HTML 中就可以很好地完成这项工作。

<div ng-repeat="modColumn in splitChannels"
  ng-class="{'col-even' : !$even, 'col-odd' : !$odd}"
  ng-style="{ width: 99 / windowAttr.columns + '%' }"
  class="cabinet">
  <div ng-repeat="channel in modColumn"
    ng-class="{'row-even' : !$even, 'row-odd' : !$odd}"
    ng-cloak
    class="panel roundnborder">
    <ng-include src="'panel.html'"></ng-include>
  </div>
</div>

如果我可以从一个 Angular 初学者那里给另一个建议,那就是:如果你的代码变得复杂,或者你正在深入研究事物的内部,退后一步,重新考虑一下你是如何使用角组件。您可能正在执行应该在另一个组件类中完成的操作。正确的 Angular 代码往往非常简洁、模块化和简单。

【讨论】:

    【解决方案3】:

    您使用模板这样做有什么原因吗?

    您不能将列数绑定到作用域上的变量吗?我做了类似的事情,只是摆弄 ng-if 指令来隐藏现在不重要的东西,或者将一般布局的东西附加到当前范围(我通常将它们全部填充到 $scope.view 上的属性中)

    还有很多这样的东西已经在 css3 的媒体选择器中工作了,根本不需要弄乱 DOM。如果没有更清楚地了解您要完成的工作,我不确定这是否是超级必要的。不止一种给猫剥皮的方法等等。

    否则,@pixelbits 是正确的 - 如果您直接摆弄 DOM 树,则需要在编译时进行 - 进入 DOM 的值进入链接。

    【讨论】:

    • 就像现在的例子,你是对的,我可以用 CSS3 媒体选择器来做。事实上,我很早就这样做了,而且效果很好。但是,我计划使行为更复杂。例如,我希望列号断点有一点滞后,因此您必须经过断点一定距离才能触发它。我不确定这在 CSS 中是否可行。至于将列数绑定到主范围,我认为这可能是要走的路。但是,我不确定如何从事件处理程序中触发编译/链接函数。
    猜你喜欢
    • 1970-01-01
    • 2013-01-29
    • 2013-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多