在一个 Angular 应用程序中,我想将一个元素从 DOM 树的一部分移动到另一部分,而不必重新加载附加到移动元素的 Controller 。
我创建了一个简化的 plunker 来说明:http://plnkr.co/edit/sqBRM3ZQ5G9xpiNd1MXm?p=preview
在这个 plunker 中,唯一要保留的数据是切换器状态,但实际上它可能是非常大量的数据,可能需要花费大量精力来初始化。
我想要做的是能够将模板 1 从指令 1 移动到指令 2,但我想保留切换器的状态。在这种情况下,如果单击切换器使其变为绿色,然后单击“从列表 1 移动到 2”两次,它会将其移动到指令 2,但会将颜色重置为红色。
为了避免这种情况,我可以在不通知 Angular 情况下移动元素,但这显然会创建一个损坏的应用程序状态。
我考虑将 Controller 从旧范围移动到新范围,但我觉得这会导致关闭问题,因为旧 Controller 可能引用不再在 DOM 中的旧元素等。
有没有解决这个问题的好方法?
添加一些代码,因为 StackOverflow 需要它,但请引用 plunker:
angular.module('app').controller('bodyController', ['$element', function ($element) {
this.contents1 = [{
path: 'template1.html'
}, {
path: 'template2.html'
}];
this.contents2 = [{
path: 'template3.html'
}, {
path: 'template4.html'
}];
this.move12 = function () {
this.contents2.push(this.contents1.pop());
};
this.move21 = function () {
this.contents1.push(this.contents2.pop());
};
this.naiveMove12 = function () {
var $elem = $($element);
$elem.find('container-list:eq(0) > div').last()
.appendTo($elem.find('container-list').eq(1));
};
this.naiveMove21 = function () {
var $elem = $($element);
$elem.find('container-list:eq(1) > div').last()
.appendTo($elem.find('container-list').eq(0));
};
this.logScope = function () {
var $elem = $($element);
[].forEach.call($elem.find('container-list'), function (elem, idx) {
console.log(idx + ': ', angular.element(elem).isolateScope().containerListCtrl);
});
};
}]);
最佳答案
根据我的经验,将应用程序状态与 View / Controller 耦合并不是最佳实践,但这里有一种方法: revised plunkr . template1.html
有一些小的变化.唯一的其他变化是 templateController.js
:
angular.module('app').controller('templateController', statefulTemplateCtrl());
function statefulTemplateCtrl() {
var state = {color: 'red'};
return function(){
this.state = state;
// this.color = 'red';
this.toggleColor = function () {
state.color = (state.color === 'red') ? 'green' : 'red';
};
};
}
函数现在用作
templateController
已配置为维护对 state
的引用目的。现在,如果您使用 bodyCtrl.move12()
移动模板,您将能够在不破坏应用程序状态的情况下保持累积的切换状态。这是因为 Controller 通过闭包引用了外部对象。因此,它可以通过 ng-include
在新的 DOM 节点上重新实例化和重新编译。无需重新启用 state
.可能是处理 的更好方法
使用服务和/或
$scope
events API 在应用程序中的不同 DOM 节点之间共享状态。假设您有模板,与您的 plunker 中引入的模板相同:
<div ng-class="{ red: (tc.color === 'red'), green: (tc.color === 'green') }" ng-click="tc.toggleColor()">Toggler</div>
你想移动
tc
包含所有切换相关状态的对象到 DOM 中的另一个节点,您可以:$scope.$broadcast('nameOfEvent', tc)
如果目标节点是源的后代$scope
$scope.$emit('nameOfEvent', tc)
如果目标节点是源的祖先 $scope
$rootScope
和 $rootScope.$broadcast('nameOfEvent', tc)
如果目标节点可以是任何一个。 调用 $scope 事件 API 的任何这些方法将允许您传输数据,在本例中为
tc
对象,从一个节点到另一个节点。与目标节点关联的目标 Controller 可以等待事件被分派(dispatch),监听器函数能够接收传输的数据:function bindDataToController(destinationCtrl){
return function(event, tc){
angular.extend(destinationCtrl, tc);
};
}
$scope.$on('nameOfEvent', bindDataToController(this));
传输的数据对象,
tc
,将在收到触发的 'nameOfEvent'
时绑定(bind)到与新目标节点关联的 Controller .现在源数据已与目标 Controller 对象合并,请确保为您的目标 Controller 提供相同的 controllerAs
名称为您的源 Controller ,在本例中为 'tc'
,以便源模板在最终编译并附加到目标 DOM 节点后可以正确引用目标 Controller (该过程将在下面讨论)。正如您所提到的,删除和添加 DOM 节点有些微不足道,所以我没有在这里讨论它,但是如果您希望轻松访问源 View /模板以根据需要在应用程序的任何位置添加和删除,您可以使用
$templateCache
.回到切换示例,如果您的切换 View /模板本身已经是使用
templateUrl
的指令的一部分文件路径为 'toggle.html'
,那么您的模板将已被放入并存储在 $templateCache
在关键 'toggle.html'
下.在您的目标 Controller 中,注入(inject)
$templateCache
,这将允许您检索模板: var template = $templateCache.get('toggle.html');
将 $compile 注入(inject)您的目标 Controller 并使用目标 $scope 对模板进行 $compile (也注入(inject)目标 Controller ):
var $template = $compile(template)($scope);
然后将编译后的模板附加到
$element
(也注入(inject)到目标 Controller 中)与您的目标 Controller 相关联:$element.append($template);
如果您只想编译和附加
'toggle.html'
收到传输的数据后将模板复制到 DOM,您可以将上述命令式编译代码放在 bindDataToController
的返回函数中,注册为 'nameOfEvent'
的事件监听器.就这样!... 一个 DOM 节点已被“移动”,具有先前在源位置建立的任何复杂状态。
关于javascript - 在 DOM 中移动 Controller 时保留元素/范围状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32579737/