How can I move an element to different places in the DOM with angular js?
I have a list of elements like so
<ul id="list" ng-controller="ListController">
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController">
<div>content</div>
<div id="overlay"></div>
</li>
</ul>
What I'm trying to acplish is moving the #overlay from place to place within the list without having to have a hidden duplicate in every item that I flag hidden/unhidden.
If this was jquery I could just do something like this:
$("#overlay").appendTo("#list li:first-child");
Is there an equivalent way to do this in angular?
How can I move an element to different places in the DOM with angular js?
I have a list of elements like so
<ul id="list" ng-controller="ListController">
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController">
<div>content</div>
<div id="overlay"></div>
</li>
</ul>
What I'm trying to acplish is moving the #overlay from place to place within the list without having to have a hidden duplicate in every item that I flag hidden/unhidden.
If this was jquery I could just do something like this:
$("#overlay").appendTo("#list li:first-child");
Is there an equivalent way to do this in angular?
Share Improve this question asked Sep 14, 2012 at 16:34 bitwitbitwit 2,6762 gold badges29 silver badges34 bronze badges 2- 3 AngularJS is approaching UI from the model-focused angle. This means that you usually manipulate your model and let AngularJS figure out how to render things. Could you please elaborate a bit on your use case, from the functional point of view, so one could propose a solution that fits nicely into the AngularJS-way? – pkozlowski.opensource Commented Sep 14, 2012 at 18:00
- Thanks for your reply. Basically the use case is that I have a long stream of social data and actions such as "tweet, share, pin" are hidden in an overlay triggered by a click or swipe within that item. The overlay should only be on one social stream item at a time. At the moment I have this "overlay" div inside of the ng-repeat call and I simply set hidden-true or hidden-false. the problem is I'm broadcasting a "closeOverlay" call to 50 - 100 items in case another one is open and that seems overboard. Managing an overlay separately and appending it as necessary seems like a better choice. – bitwit Commented Sep 14, 2012 at 18:28
2 Answers
Reset to default 11Thanks to your clarifications I can understand that you've got a list of items. You would like to be able to select one item in this list (swipe but potentially other events as well) and then display an additional DOM element (div) for a selected item. If the other item was selected it should be un-selected - this way only one item should have an additional div displayed.
If the above understanding is correct, then you could solve this with the simple ng-repeat and ng-show directives like this:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<div ng-show="isOpen(item)">overlay: tweet, share, pin</div>
</li>
</ul>
where the code in the controller would be (showing a fragment of it only):
$scope.open = function(item){
if ($scope.isOpen(item)){
$scope.opened = undefined;
} else {
$scope.opened = item;
}
};
$scope.isOpen = function(item){
return $scope.opened === item;
};
Here is the plete jsFiddle: http://jsfiddle/pkozlowski_opensource/65Cxv/7/
If you are concerned about having too many DOM elements you could achieve the same using ng-switch directive:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<ng-switch on="isOpen(item)">
<div ng-switch-when="true">overlay: tweet, share, pin</div>
</ng-switch>
</li>
</ul>
Here is the jsFiddle: http://jsfiddle/pkozlowski_opensource/bBtH3/2/
As an exercise for the reader (me), I wanted to try a custom directive to acplish this. Here is what I came up with (after many failed attempts):
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div singleton-overlay>{{item.content}}</div>
</li>
</ul>
A service is required to store the element that currently has the overlay, if any. (I decided against using the controller for this, since I think a 'service + directive' would make for a more reusable ponent than a 'controller + directive'.)
service('singletonOverlayService', function() {
this.overlayElement = undefined;
})
And the directive:
directive('singletonOverlay', function(singletonOverlayService) {
return {
link: function(scope, element, attrs) {
element.bind('click', moveOrToggleOverlay);
function moveOrToggleOverlay() {
if (singletonOverlayService.overlayElement === element) {
angular.element(element.children()).remove();
singletonOverlayService.overlayElement = undefined;
} else {
if (singletonOverlayService.overlayElement != undefined) {
// this is a bit odd... modifying DOM elsewhere
angular.element(singletonOverlayService.overlayElement.children()).remove();
}
element.append('<div>overlay: tweet, share, pin</div>')
singletonOverlayService.overlayElement = element;
jsFiddle: http://jsfiddle/mrajcok/ya4De/
I think the implementation is a bit unconventional, though... the directive not only modifies the DOM associated with its own element, but it may also modify the DOM associated with the element that currently has the overlay.
I tried setting up $watches on scope and having the singleton store and modify scope objects, but I couldn't get the $watches to fire when I changed the scope from inside the moveOrToggleOverlay function.