在不损失性能的情况下从 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 会导致无限递归,但是为该指令的每个子级调用它都可以正常工作。
这是我要解决的问题:从一组可变的小部件生成表单,其中确切的小部件及其排序由数据(即模式)指导。我采用的第一种方法看起来像(省略了不必要的细节):
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 会导致无限递归,但是为该指令的每个子级调用它都可以正常工作。