如何拦截 Angular 中的 :enter 和 :leave 动画并更改 Diff?

How to intercept :enter & :leave Animations in Angular with a changes Diff?

我的模板中有一个列表,其中将显示前 3 个项目,其余项目位于可折叠的位置,即在 'show more' link 内。

单击“显示更多”后的外观如下:

该列表是高度动态的。 这些项目由名为 myList 的单个列表中的组件保存。可折叠来自我们的模式库,其中预定义了 html 结构。所以我实际上必须在我的模板中有 2 个不同的列表。第一个用于前 3 个项目,第二个用于可折叠的其余项目。为了不使用相同的 HTML 两次,我使用 ng-template 定义列表并重复使用它 2 次。这是模板的粗略结构:

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

       ...

这是模板的用法:

<!-- 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>

此外,一旦添加了新项目,它就会进入动画列表。此外,如果一个项目被删除,它会保持动画状态。这是我的动画触发器:

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
  ])
])

问题是,当我们有超过 3 个项目并且可折叠项打开时,所有项目都在视口中看到。在这种情况下,当一个新项目被添加到列表的第一部分时,它进入动画,但由于列表的最后一个项目也离开第一个列表并进入第二个列表,它也是动画的,我们不这样做想要。

换句话说;如果我们有 5 个项目,并在顶部添加一个新项目,item3 将从第一个列表中删除,并添加到第二个列表中,这也会触发动画.

我怎样才能阻止这个动画?

今天我发现,我们可以使用此注释禁用单个项目的动画:

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

但我无法想象我应该如何实现这个标志 isAnimated 的逻辑。 item3 移到 2.list 后,我​​必须检查它是否在之前的第一个列表中,但我没有第一个列表的先前状态。

我认为唯一可能的选择是以某种方式拦截动画,并查看列表的先前状态是否具有相同的项目,如果是则中断动画..

或者我应该从项目中删除动画,而是为我的主列表 (myList) 中的更改定义一些动画。但我不知道如何使用我的模板实现这一点..

感谢任何帮助。

与您的方法略有不同。不确定它是否符合您问题的答案(拦截 enter/leave 动画事件),但我认为至少可以解决您的用例。

首先不维护 2 个数组怎么样。

<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>

并在您的组件打字稿文件中。

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

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

您的动画保持原样。现在您的功能应该与动画一起工作。由于没有项目从 1 个数组进入另一个数组的概念,您应该摆脱动画问题和它涉及的其他复杂性。

注:

与显示 More/Less 按钮相关的代码只是为了表达这个想法而进行了简化。请相应地更改代码。

希望对您有所帮助。

您遇到的问题是,您无法区分某个项目是被添加到您的列表中还是只是被移动到位,因为该项目在移动或添加之前不存在于模板中。如果该项目在移动到位之前就存在,那么您可以区分事件并相应地使用 [@.disabled]

一个简单但低效的解决方案是重复列表两次并将指示器传递给模板,通知它隐藏前三个元素或隐藏除最后三个元素之外的所有元素。同样的逻辑应该用于禁用动画。

如果我们可以假设一次只能将一个元素添加到数组中,那么一种更有效的方法是只将一个额外的元素传递给模板。对于列表的顶部,传递前四个元素,对于底部,传递从索引 2 开始的所有项目。

简化的工作示例代码

<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>

发生的事情是,索引 2 和 3 处的项目将同时出现在两个列表中,在适当的情况下在其中一个列表中隐藏并禁用动画。如果发生转变,相应的项目现在将只出现在列表中之前隐藏它们的位置。动画不会出现,因为它们已经添加到 DOM 之前。

我最初只尝试使用隐藏,但如果快速添加项目,动画的尾端就会显示。这就是 [@.disabled] 也被使用的原因。