我试图弄清楚如何在Angular 1.6.4中将数据转换为组件转换。该场景包含一个组件,一个指令(尚未重新编写为组件)和一个用于组件间通信的服务。
angular.module('app')
.service('svc', function() {
this.connector = {};
})
.directive('first', ['svc', function($svc) { return {
restrict: 'E',
scope: { 'id': '@' },
template: '<button ng-click="GetData()">get data</button>',
controller: ['$scope', 'svc', function($scope, $svc) {
$scope.connector = { data: [] };
$svc.connector[$scope.id] = $scope.connector;
$scope.GetData = function() {
// This is a mock-up; I'm really doing a REST call.
$scope.connector.data = [
{id: 0, name: 'one'},
{id: 1, name: 'two'}
];
};
}]
}; }])
.component('second', {
bindings: { parent: '@firstid' },
transclude: true,
template: '<ng-transclude></ng-transclude>',
controller: ['svc', function($svc) {
this.data = $svc.connector[this.parent];
// Not sure what to do here
}]
})
;
我的HTML看起来像这样:
<first id="first-thing"></first>
<second firstid="first-thing">
Where I expect my data to be: {{$ctrl | json}}<br/>
... but maybe here: {{$ctrl.$parent | json}}<br/>
... or even here: {{$parent | json}}<br/>
<div ng-repeat="item in $ctrl.data">
<p>Output: {{item.id}}/{{item.name}}</p>
</div>
</second>
这些可能不会与require
嵌套,这就是我使用服务存储数据的原因; <first><second></second></first>
不是一种选择。我可以在必要时使用一些$onInit
解决方法来管理从组件控制器内部的服务获取数据。我已经检查过,并且服务在正确的时间包含正确的数据。为了组件重用,我需要控制器来转换内容。
Batarang列出了我的所有范围。该指令具有范围$id
6(页面上还有其他内容),如预期的那样。该组件具有范围$id
7,如预期的那样。这些范围包含正确的数据,这些数据基于我在其中添加的内容以及我期望的内容。
我的问题是我有一个额外的范围,$id
8.它似乎是一个被转换的范围,它是一个6和7的兄弟(这些是$id
5上的同伴,我的页面控制器)。正如我在HTML snark中所指出的那样,我期望组件转换生活在7中。如果8是7的子范围,我会没事的,但它是一个断开连接的兄弟。我尝试了额外的绑定,但我不能让它们填充,所以他们只是抛出。我明显做错了,因为我得到的是1.3之前用于转换范围继承的模型。
有人可以告诉我哪些地方误入歧途或者至少指出了我正确的解决方案吗?
答案 0 :(得分:1)
我已经弄清楚了。顺便说一句,我应该注意到,根据互联网上的文献,我正在做一些我可能不应该做的事情。我理解Angular的作者来自于试图将范围隔离到链中,但我不同意该模型,至少对于包含这种模式。
angular.module('app')
.service('svc', function() {
this.connector = {};
})
.directive('first', ['svc', function($svc) { return {
restrict: 'E',
scope: { 'id': '@' },
template: '<button ng-click="GetData()">get data</button>',
controller: ['$scope', 'svc', function($scope, $svc) {
$scope.connector = { data: [] };
$svc.connector[$scope.id] = $scope.connector;
$scope.GetData = function() {
// This is a mock-up; I'm really doing a REST call.
$scope.connector.data = [
{id: 0, name: 'one'},
{id: 1, name: 'two'}
];
$scope.connector.data.Update($scope.connector.data);
};
}]
}; }])
.component('second', {
bindings: { parent: '@firstid' },
transclude: true,
template: '<ng-transclude></ng-transclude>',
controller: ['$element', '$transclude', '$compile', 'svc', function($element, $transclude, $compile, $svc) {
this.$onInit = () => { angular.extend(this, $svc.connector[this.parent]; };
var parentid = $element.attr('firstid');
$transclude((clone, scope) => {
$svc.connector[parentid].Update = (data) => {
angular.extend(scope, data);
$element.append($compile(clone)(scope));
};
});
}]
})
;
这实际上是手动转换。互联网上有太多关于人工转换的例子,人们手动修改DOM。我不完全理解为什么有些人认为这是个好主意。我们跳过这么多箍,将我们的格式(CSS)与我们的代码(Angular指令/组件)中的格式(CSS)从我们的业务逻辑(Angular服务/工厂/提供商)中分离出来,所以我不会回去将标记放入我的代码中。
我在Gustavo Henke的Angular问题上找到this article和a comment,它使用$transclude
内的范围来注册回调。有了这个关键信息,我想我可以做更多的范围操作。
$transclude
中的代码似乎超出了摘要周期。这意味着其中触及的任何内容都不会收到自动更新。幸运的是,我控制了我的数据的变化事件,所以我推动了这个回调。在回调中,数据被更改并且元素被重新编译。尚未从控制器标签绑定在服务中定位回调的关键,因此必须手动从属性中检索它。
组件不应该修改自己范围之外的数据。我特意做的不是那个。对于这样做而言,Angular似乎没有更合适的原语,而没有打破其他一些在我看来保持原样更重要的问题。
我认为这里有一个“内存泄漏”,也就是说我的元素和范围在每个更新周期都没有正确处理。我使用相当少的数据,只有用户通过节流直接更新,它在管理界面上;我可以泄漏一点内存,我不希望用户留在页面上的时间足够长,以便它能有所作为。
我的代码都希望事情在正确的位置,并在标记中命名为正确的东西。我的真实代码大约是这个行的四倍,我正在检查错误或遗漏。这不是Angular方式,这意味着我可能做错了。
如果没有Telerik article,我本可以坐在我墙上更加血腥的标记旁边。
感谢Ben Lesh的comprehensive post关于$compile
的{{1}},并提供了关于如何不使用它的适当免责声明。
Todd Motto帮助了很多人如何在his post on upgrading to 1.6中编写一个像样的Angular 1.x组件。正如人们所预料的那样,Angular documentation on components并没有提供比确切调用内容的具体指示更多的内容。
在AngularJS issue 7842的底部有一些信息可以做类似的事情,甚至可能有更好的方法比我更适当地管理范围数据。