【问题标题】:inject multiple templates as HTML into directive and compile to the directives scope将多个模板作为 HTML 注入指令并编译到指令范围
【发布时间】:2015-03-05 21:39:08
【问题描述】:

我正在开发一个自定义角度指令,其主要目标是显示数据网格。我想允许我的指令的用户将他们自己的 html 单元格模板注入其中,这将覆盖标准单元格呈现机制。我当然可以让用户在 json 配置对象中定义他们的单元格模板,并使用属性将其传输到我的指令中,但我认为更好的方法是让用户直接将他们的模板指定为 HTML。

预期用途

    <my-grid config="config" data="data">
      <cell field="active">
        <!-- User templates given here for specific columns -->
      </cell>
    </my-grid>

现在,我在指令中使用链接函数的 transclude 方法来收集指令控制器可用的&lt;cell&gt; 模板。 但是 transclude 函数返回的是编译后的 html,而 html 编译的范围当然是使用页面 &lt;my-grid&gt; 的控制器。这意味着注入的单元格模板不可能使用来自给网格指令到位。

为了实现这一点,&lt;cell&gt; 中的所有内容都需要在未编译的情况下注入。但这似乎是不可能的。我什至连接到 compile 方法,看看是否可以直接从 $element 输入参数中获取给定的&lt;cell&gt; 元素,但即使这样也为时已晚。此时,其他包含指令的模板已被包含在内,覆盖了&lt;cell&gt; 元素。

有人对我如何实现我想要做的事情有任何建议吗?

我的指令

angular.module('myApp', [])
    .controller('myController', function ($scope) {
        'use strict';

        // Simplified metadata for the grid structure
        $scope.config = {
            columns: [{name: 'id'}, {name: 'name'}, {name: 'active'}, {name: 'comment'}]
        };

        // Provide data for the directive
        $scope.data = [
            {id: 1, name: 'test1', active: true, comment: 'Contains something'},
            {id: 2, name: 'test2', active: false, comment: 'Another comment'}
        ];
    })
    .directive('myGrid', ['$timeout', '$compile', function ($timeout, $compile) {
        'use strict';
        return {
            restrict        : 'E',
            templateUrl     : 'grid/templates/gridPanel.html',
            transclude      : true,
            controllerAs    : 'gridCtrl',
            bindToController: true,
            scope           : {
                config: '=',
                data  : '='
            },
            controller      : ['$scope', '$element', function ($scope, $element) {
                var ctrl = this;
                $timeout(function () {
                    var elToReplace = $('[tpl]', $element);
                    elToReplace.replaceWith(ctrl.getTemplate($compile(elToReplace.attr('tpl'))($scope)));
                });
                ctrl.getTemplate = function (column) {
                    return (ctrl.templates[column] ? ctrl.templates[column] : ctrl.templates['__ALL__']);
                }
            }],
            compile         : function compile ($element, $attrs, transclude) {
                var origEl = $element; // $element allready contains the directive template here. <cell> is unobtainable.
                return function postLink ($scope, $element, $attrs, ctrl, $transclude) {

                    // This provides a list of <cell> elements, but they are allready compiled in the scope they are provided in.
                    $transclude(function (overrides) {
                        ctrl.templates = _.chain(overrides)
                            .filter(function (content) {
                                return content.nodeName.toUpperCase() === 'CELL';
                            })
                            .indexBy(function (content) {
                                return $(content).attr('field');
                            })
                            .value();
                        ctrl.templates['__ALL__'] = '<span>{{ row[column.name] }}</span>';
                    });
                }
            }
        };
    }])
    .run(['$templateCache', function ($templateCache) {
        'use strict';
        $templateCache.put('grid/templates/gridPanel.html',
            '<div class="panel panel-default">' +
            '  <header class="panel-heading">' +
            '    TEST-GRID' +
            '  </header>' +
            '  <div class="panel-body">' +
            '    <table class="table table-condensed">' +
            '      <thead>' +
            '        <tr>' +
            '          <th ng-repeat="column in gridCtrl.config.columns track by column.name">' +
            '            {{ column.name }}' +
            '          </th>' +
            '        </tr>' +
            '      </thead>' +
            '      <tbody>' +
            '        <tr ng-repeat="row in gridCtrl.data track by $index">' +
            '          <td ng-repeat="column in gridCtrl.config.columns track by column.name">' +
            '            <span tpl="column"></span>' + // This is where the cell template should be rendered.
            '          </td>' +
            '        </tr>' +
            '      </tbody>' +
            '    </table>' +
            '  </div>' +
            '</div>');
    }]);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>

<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.3.1/lodash.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>

<div ng-app="myApp" ng-controller="myController">
  <div class="container-fluid">
    <h2>My grid setup</h2>
    <my-grid config="config" data="data">
      <cell field="active">
        <label class="checkbox">
          <input type="checkbox" ng-model="row.active" />
        </label>
      </cell>
    </my-grid>
  </div>
</div>

这不起作用。首先,它没有按照应有的方式嵌入模板。它只嵌入了我设置为“默认模板”的内容,并且被嵌入为明文。未编译。如果我尝试在范围内编译它,角度会产生一个不确定的摘要循环。

【问题讨论】:

  • 你为什么不让他们给你一个html字符串,然后用它来编译和使用。我不认为我完全理解你想要做什么以及你希望人们如何给你 html。
  • 正如我在问题中所说,我可以让他们在 json 配置字符串中注入 HTML 并将其作为属性传递给指令(参见上面的 params=""),但我认为更好的方法是让用户将他们的模板指定为 HTML(参见上面的单元格元素的内容)
  • 也许你应该为你的问题创建一个plunker。您可以尝试使用 textarea 而不是将其 html 作为字符串提供给您的单元格,然后您可以编译它。很难说,你的问题和需求都不清楚。
  • 包含了一个可运行的代码 sn-p,它描述了我的指令以及它应该如何工作

标签: javascript angularjs templates angularjs-directive


【解决方案1】:

修复它。秘密是 transclude 函数接收范围。我只需要保存它,并在编译模板时使用它。 :-)

下面是我的指令的一个版本,它按预期工作!

angular.module('myApp', [])
    .controller('myController', function ($scope) {
        'use strict';

        // Simplified metadata for the grid structure
        $scope.config = {
            columns: [{name: 'id'}, {name: 'name'}, {name: 'active'}, {name: 'comment'}]
        };

        // Provide data for the directive
        $scope.data = [
            {id: 1, name: 'test1', active: true, comment: 'Contains something'},
            {id: 2, name: 'test2', active: false, comment: 'Another comment'}
        ];
    })
    .directive('myGrid', ['$timeout', '$compile', function ($timeout, $compile) {
        'use strict';
        return {
            restrict        : 'E',
            templateUrl     : 'grid/templates/gridPanel.html',
            transclude      : true,
            controllerAs    : 'gridCtrl',
            bindToController: true,
            scope           : {
                config: '=',
                data  : '='
            },
            link            : function postLink ($scope, $element, $attrs, ctrl, $transclude) {
                // Set default cell template
                var defaultTemplates = [
                    $('<cell field="__ALL__"><span>{{ row[column.name] }}</span></cell>').get(0)
                ];
                // Collect cell templates given -----------------
                $transclude(function(overrides, futureScope) {
                    var transcluded = {
                        tpls: _.filter(overrides, function (content) {
                            return content.nodeName.toUpperCase() === 'CELL';
                        }),
                        scope: futureScope
                    };
                    transcluded.tpls = transcluded.tpls.concat(defaultTemplates); // Insert defaults
                  
                    // Expose cell templates to controller scope
                    ctrl.cellTemplates = _.chain(transcluded.tpls)
                        // Group by column name
                        .groupBy(function(tpl)   { return $(tpl).attr('field'); })
                    
                        // Create the template object
                        .mapValues(function(tpl) {
                            var template = $(tpl).html().trim();
                            return {
                                tpl: template,
                                isTranscluded: $(tpl).hasClass('ng-scope'),
                                origScope: ($(tpl).hasClass('ng-scope') ? transcluded.scope : null), // Save the original scope the template was given in
                                compiledFn: $compile(template) // Precompile templates
                            };
                        })
                        .value();
                    
                    // Create convenience method to retrieve template based on column name
                    ctrl.getTemplate = function (column) {
                        return _.has(ctrl.cellTemplates, column.name) ? ctrl.cellTemplates[column.name] : ctrl.cellTemplates.__ALL__;
                    }

                });

            },
            controller      : ['$scope', '$element', function ($scope, $element) {
                var ctrl = this;
            }]
        };
    }])
    .directive('myGridCell', function () {
        'use strict';
        return {
            restrict: 'A',
            require : '^^myGrid',
            scope   : {
                column  : '=myGridCell', // This is the configuration object for this column
                row     : '=',           // Contains the data for the entire row
                rowIndex: '='            // The row index in the grids dataset
            },
            link: function ($scope, $element, $attrs, ctrl) {
                var me = {
                    childScope: null,
                    renderTemplate: function () {
                        $element.empty();
                        // Retrieve template for the given column
                        var tpl = ctrl.getTemplate($scope.column);

                        // Cleanup old fun
                        if (me.childScope && me.childScope.$id !== $scope.$id) {
                            me.childScope.$destroy();
                        }
                        // Get or create the child scope
                        if (tpl.isTranscluded) {
                            // Create a new scope as a child of the one transcluded
                            me.childScope            = tpl.origScope.$new();
                            
                            // Attach important properties to newly created scope
                            me.childScope.row        = $scope.row;
                            me.childScope.column     = $scope.column;
                            me.childScope.rowIndex   = $scope.rowIndex;
                        } else {
                            me.childScope = $scope;
                        }
                        // Attach controller to childScope
                        me.childScope.gridCtrl = ctrl;

                        // Apply the template in the appropriate scope
                        tpl.compiledFn(me.childScope, function (clonedElement, scope) {
                            scope.$element = clonedElement;
                            $element.append(clonedElement);
                        });
                    }
                }
                me.renderTemplate();
            }
        }
    })
    .run(['$templateCache', function ($templateCache) {
        'use strict';
        $templateCache.put('grid/templates/gridPanel.html',
            '<div class="panel panel-default">' +
            '  <header class="panel-heading">' +
            '    TEST-GRID' +
            '  </header>' +
            '  <div class="panel-body">' +
            '    <table class="table table-condensed">' +
            '      <thead>' +
            '        <tr>' +
            '          <th ng-repeat="column in gridCtrl.config.columns track by column.name">' +
            '            {{ column.name }}' +
            '          </th>' +
            '        </tr>' +
            '      </thead>' +
            '      <tbody>' +
            '        <tr ng-repeat="(rowIndex, row) in gridCtrl.data track by $index">' +
            '          <td ng-repeat="column in gridCtrl.config.columns track by column.name" my-grid-cell="column" row="row" row-index="rowIndex">' +
            '          </td>' +
            '        </tr>' +
            '      </tbody>' +
            '    </table>' +
            '  </div>' +
            '</div>');
    }]);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>

<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.3.1/lodash.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>

<div ng-app="myApp" ng-controller="myController">
  <div class="container-fluid">
    <h2>My grid setup</h2>
    <my-grid config="config" data="data">
      <cell field="active">
        <label class="checkbox">
          <input type="checkbox" ng-model="row.active" />
        </label>
      </cell>
    </my-grid>
  </div>
</div>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多