4

It is possible to use an ng-repeat to achieve the following compiled DOM:

<div class="container">
    <!-- ngRepeat item in items -->
    <div ng-repeat="item in items">Item 1</div>
    <!-- end ngRepeat: item in items -->
    <!-- ngRepeat item in items -->
    <div ng-repeat="item in items">Item 2</div>
    <!-- end ngRepeat: item in items -->
    <!-- ngRepeat item in items -->
    <div ng-repeat="item in items">Item 3</div>
    <!-- end ngRepeat: item in items -->
    <div class="wrapper">
        <!-- ngRepeat item in items -->
        <div ng-repeat="item in items">Item 4</div>
        <!-- end ngRepeat: item in items -->
        <div ng-repeat="item in items">Item 5</div>
        <!-- end ngRepeat: item in items -->
   </div>
</div>

i.e. to have the last n items wrapped in an another element.

It might seem like a strange request and I understand it would be trivial to achieve this using two ng-repeat directives. However, it needs to be a single ng-repeat so that I can move items in and out of the wrapper without them being added and removed from the DOM (in a manner described here).

What I'm trying to achieve is a news-ticker style scrolling effect by giving the .wrapper element overflow:hidden and using javascript animate the top position of the child elements. To be honest I'd rather not have to have a wrapper element at all but I'm not sure there is any other way to achieve the scrolling effect I require. Perhaps manipulating the clip property to achieve the effect could work but I'm not entirely sure.

So it is possible to apply a wrapper element to some items in an ng-repeat?

Community
  • 1
  • 1
djskinner
  • 8,035
  • 4
  • 49
  • 72
  • could you use $last to achive what you want? https://docs.angularjs.org/api/ng/directive/ngRepeat or check $index? – Millard Apr 30 '15 at 15:47
  • I did attempt to combine `$index` and `ng-if` but without any success. e.g. `
    ` and then `
    ` but the `ng-if` directive isn't designed to work like that and doesn't like it.
    – djskinner Apr 30 '15 at 15:56

4 Answers4

2

Unfortunately, when you change the parent of an element in the visual tree, it must be removed and re-added. Among other problems, consider how styling rules might be applied differently depending on who is whose parent. It's also a pretty expensive thing to do in an animation.

Disappointing? Perhaps.

But reading through your use case, I think you'll find Angular is very good at handling this sort of animation with just a tiny bit of code. I've included a fiddle for you to play with:

http://jsfiddle.net/wjxgcb0k/

It's very easy to create and bind your own layout to the elements on the page. We'll first use ng-repeat to spit out one row per item:

<div class="container" ng-app ng-controller="Foo">
    <div class="item"
        ng-repeat="item in items" 
        ng-style="{'top': item.top + 'px'}">{{item.name}}
    </div>
</div>

Because we want to handle our own layout, each item in the container will be position: absolute. See how we bind the top to item.top + 'px'? All we need to do is adjust those top values in an animation loop. I'm going to use requestAnimationFrame because it's my go to tool for manual animation, but you can use css transitions or animations if you are more comfortable.

I'll initialize the top values in the controller. That's what it's for, holding state:

$scope.items.forEach(function(item, idx) {
    item.h = height;
    item.top = idx * (height + margin);
    console.log(item);
});

And then I'll set up an animation loop:

var tick = function() {
    $scope.$apply(function() {
        $scope.items.forEach(function(item, idx) {
            item.top -= velocity;
            if (item.top < -(height + margin)) {
                item.top += $scope.items.length * (height + margin);
            }
        });
    });
    requestAnimationFrame(tick);
};

And then kick off the whole thing:

requestAnimationFrame(tick);    

Some neat improvements you can make with this:

  • Consider only animating as many items as will fit on the page, rather than all of the items in the collection. Performance will thank you.
  • Instead of relying on the $apply to propagate changes to the Dom, directly manipulate the style yourself. This can improve animation performance.
  • When the if condition fires and we reset the item to the bottom of the ticker, we might check to see if there is different content to be put into the ticker item. This way you might have a live updating ticker that changes over time.
  • Try your hand at making this all work horizontally. Or perhaps adjust the opacity as top approaches zero or the bottom half of the list.

I hope this helps, and I hope that the small amount of code needed to do this will encourage you to leave behind the notion of relying on the HTML to do your layout for you.

Michael Hays
  • 6,878
  • 2
  • 21
  • 17
  • On the face of it this looks like the best approach to take. I will have a more detailed look at the fiddle and see if I can get the desired effect using the advice you have provided. Thanks for pointing out the fact that changing the parent in the visual tree does require a remove and re-add. If the scrolling effect requires a container with `overflow:hidden` then I guess there is no way to get around this fact. Additional kudos for reminding me of `requestAnimationFrame`. Animation performance is critical in my application and this may come in useful. – djskinner Apr 30 '15 at 16:53
0

I actually don't know whether it is possible to wrap N items inside block using ng-repeat. But what can be done is to apply .wrapper class to every element with index higher than 'some value'. That can be done using ng-class and $index.

edit: hope I understood how new-ticker works. If not, sorry for the wrong answer.

plnkr

<div ng-repeat="op in options" ng-class="{wrapper: $index > limit}">{{op.title}}</div>

in the sample you can change model value of 'limit' variable to change number of visible items.

or better yet. If the items inside of the wrapper should "be appearing", maybe easiest approach would be

ng-hide="$index > end || $index < start"

This would hide items at the start and at the end. Manipulating values of 'start' and 'end' would create the effect.

  • I'm not sure this will achieve what I am looking for as the wrapper class will be applied to each item whereas I require a single element to wrap around multiple (but not all) items in the repeat. Perhaps a code sample would allow me to better understand if this approach will work? – djskinner Apr 30 '15 at 16:00
0

Technically, the answer is no because the wrapper exists outside the scope of the ngRepeat. So it's not possible to set conditions on the wrapper based upon a property of each item.

You can on the other hand still reproduce the same markup. It just requires filtering and some creative thinking.

 <div class="container">
      <!-- items with wrapper = false are outside wrapper -->
      <div ng-repeat="item in items | {'wrapper':false}">{{item}}</div>
      <div class="wrapper">
          <!-- items with wrapper = true are inside wrapper -->
          <div ng-repeat="item in items | {'wrapper':true}">{{item}}</div>
      </div>
 </div>
Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • The problem I have with using two `ng-repeat`s is that toggling the wrapper property to move the item in or out of the wrapper will cause the DOM for that item to be destroyed and recreated as it moves between the two `ng-repeat`s. This is really problematic for my use case with the main issue being that it makes it impossible to animate the movement. – djskinner Apr 30 '15 at 16:13
  • 1
    @djskinner you can't animated the changing of parents in the DOM, but I feel your pain. I'm working on a project right now where using parents gives me the layout I want, but I also need to animate the changing of parents. In the end, I'll have to manually set the positions of everything to represent the layout I want without the parents in the way. – Reactgular Apr 30 '15 at 16:52
  • Thanks, I understand now that animating a changing of parents is not possible. This is a useful insight. Now I wonder if it is possible to achieve the animated scrolling of the last `n` items in a list without resorting to a wrapper element. I feel like it might be possible by animating both `top` and `clip-path` of the first and last visible items but will need to investigate further. – djskinner Apr 30 '15 at 16:59
0

Can you achieve this using CSS and nth:child to access the last two elements? You can make the visibility: hidden, for these two nodes in the ng-repeat. You can even do CSS animation with delays.

Community
  • 1
  • 1
Tara Lerias
  • 239
  • 1
  • 8