我正在尝试制作一个最小但很花哨的AngularJS教程示例,我遇到了一个问题,在更新模型的整个树之后(在ng-change update的范围内),一个驱动的模板顶级ng-repeat根本不会重新渲染。
但是,如果我在战略位置添加代码$scope.data = {}
,它就会开始工作;但随后显示屏闪烁而不是光滑。并且它不是AngularJS自动数据绑定如何工作的一个很好的例子。
我缺少什么;什么是正确的解决方案?
确切代码 - 从下拉列表中选择一个国家/地区 - 这个jsFiddle不起作用:http://jsfiddle.net/f9zxt36g/ 这个jsFiddle工作但闪烁:http://jsfiddle.net/y090my10/
var app = angular.module('factbook', []);
app.controller('loadfact', function($scope, $http) {
$scope.country = 'europe/uk';
$scope.safe = function safe(name) { // Makes a safe CSS class name
return name.replace(/[_\W]+/g, '_').toLowerCase();
};
$scope.trunc = function trunc(text) { // Truncates text to 500 chars
return (text.length < 500) ? text : text.substr(0, 500) + "...";
};
$scope.update = function() { // Handles country selection
// $scope.data = {}; // uncomment to force rednering; an angular bug?
$http.get('https://rawgit.com/opendatajson/factbook.json/master/' +
$scope.country + '.json').then(function(response) {
$scope.data = response.data;
});
};
$scope.countries = [
{id: 'europe/uk', name: 'UK'},
{id: 'africa/eg', name: 'Egypt'},
{id: 'east-n-southeast-asia/ch', name: 'China'}
];
$scope.update();
});
模板由ng-repeat驱动:
<div ng-app="factbook" ng-controller="loadfact">
<select ng-model="country" ng-change="update()"
ng-options="item.id as item.name for item in countries">
</select>
<div ng-repeat="(heading, section) in data"
ng-init="depth = 1"
ng-include="'recurse.template'"></div>
<!-- A template for nested sections with heading and body parts -->
<script type="text/ng-template" id="recurse.template">
<div ng-if="section.text"
class="level{{depth}} section fact ng-class:safe(heading);">
<div class="level{{depth}} heading factname">{{heading}}</div>
<div class="level{{depth}} body factvalue">{{trunc(section.text)}}</div>
</div>
<div ng-if="!section.text"
class="level{{depth}} section ng-class:safe(heading);">
<div class="level{{depth}} heading">{{heading}}</div>
<div ng-repeat="(heading, body) in section"
ng-init="depth = depth+1; section = body;"
ng-include="'recurse.template'"
class="level{{depth-1}} body"></div>
</div>
</script>
</div>
我错过了什么?
答案 0 :(得分:1)
您通过在section
指令section = body;
内执行ng-if
来更改$scope
属性的引用。细节上发生了什么(https://docs.angularjs.org/api/ng/directive/ngIf):
ng-repeat
data
为$scope
创建了ng-repeat
,其中包含heading
和section
;
ng-include
$compile
'的模板,第一步来自$scope
; ng-if
使用继承创建自己的$scope
并重复heading
和section
; ng-repeat
的模板中section = body
并更改了对section
内的ngIf.$scope
属性的引用; section
是继承的属性,因此您定向的是显示来自其他section
的{{1}}属性,与$scope
的父级的初始$scope
不同。This is easily traced - 只需添加:
ngIf
并且您会注意到...
<script type="text/ng-template" id="recurse.template">
{{section.Background.text}}
...
实际上指定了正确的值并相应地进行了更改,而section.Background.text
下的section.text
并未改变。
无论您更新ngIf.$scope
引用,$scope.data
都不关心,因为它自己的ng-if
仍然引用了垃圾收集器未清除的上一个对象。
<强> Reccomdendation:强> 不要在模板中使用递归。序列化您的响应并创建将在不需要递归的情况下显示的平面对象。您希望模板显示静态标题和动态文本。这就是为什么你有落后的渲染 - 你没有使用单向绑定来处理诸如节标题之类的静态内容。 Some performance tips
P.S。在管理数据时,不要在模板中进行递归,而是在业务逻辑位置进行递归。 ECMAScript对引用非常敏感,最佳实践是保持模板简单 - 模板中没有赋值,没有变异,没有业务逻辑。当你在section
更新你的每一个$watcher
这么多次没有结束时,Angular也会与section
一起狂野。
答案 1 :(得分:0)
感谢Apperion和anoop的分析。我已经缩小了问题的范围,结果是ng-repeat和ng-init之间似乎存在错误的交互,这会阻止在ng-init中复制重复变量时应用更新。这是一个最小化的示例,它显示了不使用任何递归或包含或阴影的问题。 https://jsfiddle.net/7sqk02m6/
<div ng-app="app" ng-controller="c">
<select ng-model="choice" ng-change="update()">
<option value="">Choose X or Y</option>
<option value="X">X</option>
<option value="Y">Y</option>
</select>
<div ng-repeat="(key, val) in data" ng-init="copy = val">
<span>{{key}}:</span> <span>val is {{val}}</span> <span>copy is {{copy}}</span>
</div>
</div>
控制器代码只是在&#34; X&#34;之间切换数据。和&#34; Y&#34;和空版本:
var app = angular.module('app', []);
app.controller('c', function($scope) {
$scope.choice = '';
$scope.update = function() {
$scope.data = {
X: { first: 'X1', second: 'X2' },
Y: { first: 'Y1', second: 'Y2' },
"": {}
}[$scope.choice];
};
$scope.update();
});
请注意{{copy}}
和{{val}}
在循环中的行为应该相同,因为copy
只是val
的副本。它们只是'X1'
之类的字符串。事实上,第一次选择&#39; X&#39;时,效果很好 - 制作副本,它们遵循循环变量并通过循环更改值。 val和副本是相同的。
first: val is X1 copy is X1
second: val is X2 copy is X2
但是当你更新到&#39; Y&#39;数据的版本,{{val}}
变量更新为Y版本,但{{copy}}
值不更新:它们保留为X版本。
first: val is Y1 copy is X1
second: val is Y2 copy is X2
同样,如果您清除所有内容并以“&#39; Y&#39;”开头,则更新为“X&#39;”,副本会被卡在Y版本中。
结果是:ng-init
似乎无法在这种情况下复制循环变量时以某种方式正确设置观察者。我不能很好地遵循Angular内部,以了解bug的位置。但是避免ng-init
解决问题。原始示例的一个版本在没有闪烁的情况下效果很好:http://jsfiddle.net/cjtuyw5q/
答案 2 :(得分:0)
如果您想控制ng-repeat正在跟踪哪些键,您可以使用trackby语句:https://docs.angularjs.org/api/ng/directive/ngRepeat
<div ng-repeat="model in collection track by model.id">
{{model.name}}
</div>
修改其他属性不会触发刷新,这可能对性能非常有利,或者如果您对对象的所有属性进行搜索/过滤,则会很痛苦。