在不损失性能的情况下从 json 数据动态生成小部件

Dynamically generating widgets from json data without performance losses

这是我要解决的问题:从一组可变的小部件生成表单,其中确切的小部件及其排序由数据(即模式)指导。我采用的第一种方法看起来像(省略了不必要的细节):

controller.js:

 angular.module('app').controller(function($scope) {
  $scope.data = {
    actions: [{
      name: 'Action1',
      base: 'nova.create_server',
      baseInput: {
        flavorId: {
          title: 'Flavor Id',
          type: 'string'
        },
        imageId: {
          title: 'Image Id',
          type: 'string'
        }
      },
      input: [''],
      output: [{
          type: 'string',
          value: ''
        }, {
          type: 'dictionary',
          value: {
            key1: '',
            key2: ''
          }
        }, {
          type: 'list',
          value: ['', '']
        }]
    }]
  };

  $scope.schema = {
    action: [{
      name: 'name',
      type: 'string',
    }, {
      name: 'base',
      type: 'string',
    }, {
      name: 'baseInput',
      type: 'frozendict',
    }, {
      name: 'input',
      type: 'list',
    }, {
      name: 'output',
      type: 'varlist',
    }
    ]
  };
})

template.html

      <div ng-controller="actionCtrl" ng-repeat="item in data.actions">
        <div ng-repeat="spec in schema.action" ng-class="{'right-column': $even && isAtomic(spec.type), 'left-column': $odd && isAtomic(spec.type)}">
           <typed-field></typed-field>
           <div class="clearfix" ng-show="$even"></div>
        </div>
      </collapsible-panel>

directives.js

.directive('typedField', function($http, $templateCache, $compile) {
  return {
    restrict: 'E',
    scope: true,
    link: function(scope, element, attrs) {
      $http.get(
        '/static/mistral/js/angular-templates/fields/' + scope.spec.type + '.html',
        {cache: $templateCache}).success(function(templateContent) {
          element.replaceWith($compile(templateContent)(scope));
        });
    }
  }
})

在位于“/fields/”内的模板中,字符串类型字段的最简单模板是

<div class="form-group">
  <label>{$ spec.title || makeTitle(spec.name) $}</label>
  <input type="text" class="form-control" ng-model="item[spec.name]">
</div>

此方法适用于 - 所有小部件都已呈现,模型绑定有效,但是一旦我在这些小部件中键入单个字母,范围就会发生变化并且小部件会重新绘制,从而导致: * 失去焦点 * 一些时间延迟有效意味着性能不佳。

为了克服这个缺点,我按以下方式重写了我的应用程序:

template.html

      <div ng-controller="actionCtrl" ng-repeat="item in data.actions">
        <action></action>
      </div>

directives.js

.directive('typedField', function($http, $templateCache, idGenerator, $compile) {
  return {
    restrict: 'E',
    scope: true,
    compile: function ($element, $attrs) {
      $http.get(
        '/static/mistral/js/angular-templates/fields/' + $attrs.type + '.html',
        {cache: $templateCache}).success(function (templateContent) {
          $element.replaceWith(templateContent);
        });
      return function (scope, element, attrs) {
        scope.title = $attrs.title;
        scope.type = $attrs.type;
        scope.name = $attrs.name;
      }
    }
  }
})

.directive('action', function($compile, schema, isAtomic) {
  return {
    restrict: 'E',
    compile: function(tElement, tAttrs) {
      angular.forEach(
        schema.action,
        function(spec, index) {
            var cls = '', elt;
            if ( isAtomic(spec.type) ) {
              cls = index % 2 ? 'class="right-column"' : 'class="left-column"';
            }
            elt = '<div ' + cls + '><typed-field type="' + spec.type + '" name="' + spec.name + '"></typed-field>';
            if ( index % 2 ) {
              elt += '<div class="clearfix"></div>';
            }
            elt += '</div>';
            tElement.append(elt);
        });
      return function(scope, element, attrs) {
      }
    }
  }
})

我不是从作用域获取架构,而是通过依赖注入将其提供到指令的编译阶段(仅在第一次运行 - 这似乎是我需要避免重复完全重绘小部件所需要的东西) .但是现在我得到的不是漂亮的小部件(和以前一样),而是原始的 html 数据绑定根本没有被评估。我想我做错了什么,但无法理解我应该如何正确使用编译函数来避免性能问题。您能否就应该修复的问题给出建议?

我终于找到了在那种情况下指令的编译函数应该返回什么

  .directive('action', function($compile, schema, isAtomic) {
    return {
      restrict: 'E',
      compile: function(tElement, tAttrs) {
        angular.forEach(
          schema.action,
          function(spec, index) {
            var cls = '', elt;
            if ( isAtomic(spec.type) ) {
              cls = index % 2 ? 'class="right-column"' : 'class="left-column"';
            }
            elt = '<div ' + cls + '><typed-field type="' + spec.type + '" name="' + spec.name + '"></typed-field>';
            if ( index % 2 ) {
              elt += '<div class="clearfix"></div>';
            }
            elt += '</div>';
            tElement.append(elt);
         });
         var linkFns = [];
         tElement.children().each(function(tElem) {
           linkFns.push($compile(tElem));
         });
         return function(scope) {
           linkFns.forEach(function(linkFn) {
             linkFn(scope);
           });
         }
      }
   }
})

实际上 $compile 所做的是调用它遇到的每个指令的 'compile' 函数 - 因此在当前指令的模板上调用 $compile 会导致无限递归,但是为该指令的每个子级调用它都可以正常工作。