3

I have a list in my template, where the first 3 items will be shown and the rest is in a collapsible, i.e. within a 'show more' link.

  • item1
  • item2
  • item3

    Show More

Here is how it looks when you click Show More:

  • item1
  • item2
  • item3
  • item4
  • item5

    Show Less

The list is highly dynamic. The items are held by component in a single list called myList. The collapsible is from our pattern library where the html structure is predefined. So i actually had to have 2 different lists in my template. First is for the first 3 items, second is for the rest within a collapsible. In order not to use the same HTML twice, i define the list with ng-template and reuse it 2 times. Here is the template's rough structure:

<ng-template #listRef let-list>
  <ul>
    <ng-container *ngFor="let item of list; trackBy: trackByFn;">
      <li @animation>

       ...

Here is the usage with template:

<!-- first part, max 3 items -->

<ng-container *ngTemplateOutlet="listRef; context: {$implicit: myList?.slice(0,3)}"></ng-container>


<!-- second part, rest in collapsible -->

<ng-container *ngIf="lebenslaufEintraege?.length > 3">
  <div class="my-collapsible">
    <a>...</a>
    <div>..
      <ng-container
          *ngTemplateOutlet="listRef; context: {$implicit: myList?.slice(3,myList.length)}">
      </ng-container>

Also, as soon as a new item is added it enters to the list animated. Also if an item is removed, it leaves animated. Here is my animation trigger:

trigger('animation', [
  transition(':enter', [
    style({ height: '0px', 'padding-top': '0', 'padding-bottom': '0'}),  // initial
    animate('0.5s',
      style({ height: '*', 'padding-top': '*', 'padding-bottom': '*'}))  // final
  ]),
  transition(':leave', [
    style({ height: '*', 'padding-top': '*', 'padding-bottom': '*', opacity: 1}),  // initial
    animate('0.5s',
      style({ height: '0px', 'padding-top': '0', 'padding-bottom': '0', opacity: 0}))  // final
  ])
])

The problem is then, when we have more than 3 items and the collapsible is open, so all items are seen in viewport. In this case, when a new item is added to the first part of the list, it enters animated, but since the last item of the list also leaves the first list and enters to second list, it is also animated, which we do not want.

In another words; if we have 5 items, and add a new one to the top, item3 is removed from the first list, and added to the second list, which also triggers the animation.

How can i prevent this animation?

Today i have found out, that we can disable the animation for the individual items with this annotation:

<li [@.disabled]="item.isAnimated" @animation>

But i cannot imagine how i should implement the logic for this flag isAnimated. As soon as the item3 is shifted to the 2. list, i have to check if it was in the first list before, but i don't have the previous status of the first list.

I think the only possible option is to somehow intercept the animation, and look if the previous state of the list had the same item, and if yes interrupt the animation..

Or should i remove the animation from the item, and instead define some animation for the changes in my main-list (myList). But i do not know how to achieve this in my case with my template..

I appreciate any help.

akcasoy
  • 6,497
  • 13
  • 56
  • 100

2 Answers2

3

The problem you have is that you can't distinguish if an item is being added to your list or simply being shifted into place because the item doesn't exist in the template prior to the shift or the add. If the item existed before it was shifted into place then you could distinguish the events and use [@.disabled] accordingly.

A simple, but inefficient solution would be to repeat the list twice and pass an indicator to the template informing it to either hide the first three elements or all elements but the last three. The same logic should be for disabling animation.

If we can assume that only one element can be added to the array at a time, then a more efficient approach would be to only pass only one extra element to the template. For the top portion of the list pass the first four elements, and for the bottom pass all items starting from index two.

Simplified Working Example Code

<ng-container *ngTemplateOutlet="listRef; context: {$implicit: items | slice:0:3, hideIndex: 3}"></ng-container>
<ng-container *ngTemplateOutlet="listRef; context: {$implicit: items | slice:2, hideIndex: 0}"></ng-container>

<ng-template #listRef let-list let-hideIndex="hideIndex">
  <ul>
    <ng-container *ngFor="let x of list; let index = index">
      <li @animation 
          [hidden]="hideIndex == index"
          [@.disabled]="hideIndex == index">{{x}}
      </li>
    </ng-container>
  </ul>
</ng-template>

What is happening is that the items at index 2 and 3 will be in both lists, hidden and with disabled animation in one of them where appropriate. If a shift occurs the appropriate item will now only be in the list where they were previously hidden. The animation doesn't occur because they were already added to the DOM earlier.

I originally tried just using hidden, but if items were added quickly then the tail end of the animation would been shown. That is why the [@.disabled] is used as well.

Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
0

Slightly different approach from yours. Not sure if it qualifies as an answer to your question (intercepting enter/leave animation event) but I think will solve your use case at least.

How about not maintaining 2 arrays in first place.

<div *ngFor="let item of list; let i=index; trackBy: trackByFn;">
    <li @animation *ngIf="i < defaultCount">
        {{item.value}}
    </li>
</div>

<button (click)="showMore()" *ngIf="defaultCount === 3">Show More</button>
<button (click)="showLess()" *ngIf="defaultCount !== 3">Show Less</button>

And in your component typescript file.

showMore(): void {
    this.defaultCount = this.list.count;
}

showLess(): void {
    this.defaultCount = 3;
}

Your animation remains as is the way you had that implemented. Now your functionality should work along with animation. And as there is no concept of items going out of 1 array and entering another, you should get rid of the animation issue and other complexities it involves.

Note:

Code related to Show More/Less button are just simplified to express the idea. Please change the code accordingly.

Hope it helps.

Sandy
  • 11,332
  • 27
  • 76
  • 122
  • 1
    That's mean for no reason. I am not sure how 2 lists in a template is related to this answer and I had no intentions to live with the cases you will have to live with. I was answering the question with honesty. And I am not very often to SO because I have more important things in life than 50 points of yours. All the best with whatever you do and the way you think. – Sandy Mar 18 '19 at 17:37