首页 文章

将多个模板作为HTML注入指令并编译到指令范围

提问于
浏览
0

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

Intended use

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

现在,我在我的指令中使用link函数的transclude方法来收集指令控制器可用的 <cell> 模板 . 但是transclude函数返回编译的html,编译html的范围当然是页面 <my-grid> 的控制器 . 这意味着注入的单元格模板不可能使用给予grid指令的数据中的值进行转换到位 .

为了实现这一目标, <cell> 中的所有内容都需要注入未编译的内容 . 但这似乎是不可能的 . 我甚至连接到编译方法,看看我是否可以直接从$ element输入参数获得给定的 <cell> 元素,但即使这样也为时已晚 . 此时,已经包含了来自其他包含指令的模板,覆盖了 <cell> 元素 .

有没有人有任何关于如何实现我想要做的事情的提示?

My directive

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>

这不起作用 . 首先,它不会按原样转换模板 . 它只会转换我设置为“默认模板”的内容,并将其作为明文进行转换 . 没编译 . 如果我尝试在范围内编译它,则角度会产生一个不定的摘要循环 .

1 回答

  • 0

    固定它 . 秘密是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>
    

相关问题