You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 12升级至18后懒加载组件导航未卸载(动画相关)

问题分析与解决方案

从Angular 12升级到18后,懒加载模块组件在导航时未被卸载反而重复追加,核心原因是ColumnComponent中的fillState动画在组件销毁时未被正确终止,导致Angular动画模块仍持有组件实例或DOM的引用,阻止了正常卸载流程。Angular 12到18间,动画模块的清理逻辑做了优化,对活跃动画的组件销毁处理更严格,旧版本中未暴露的引用泄漏问题在新版本中显现。

解决方案

方案1:组件销毁时手动终止动画

在ColumnComponent中实现OnDestroy钩子,手动停止活跃的动画播放器,释放引用以确保组件正常卸载:

import { Component, Input, HostBinding, OnDestroy } from '@angular/core';
import { trigger, state, style, animate, transition, AnimationEvent } from '@angular/animations';

@Component({
  selector: 'gizmonic-column',
  templateUrl: './column.component.html',
  animations: [
       trigger('fillState', [
           state('on', style({
               maxWidth: '2000px'
           })),
           state('off', style({
               maxWidth: '768px'
           })),
           transition('on => off', animate('0.25s ease-in')),
           transition('off => on', animate('0.25s ease-out'))
       ])
  ],
})
export class ColumnComponent implements OnDestroy {
  @Input()
  @HostBinding('attr.display-mode')
  public displayMode: "focused" | "foreground" | "background" = "foreground";

  @Input()
  public showHeader: boolean = true;

  @Input()
  @HostBinding('attr.header-display-mode')
  headerDisplayMode: "full" | "wrapped" = "full";

  @Input()
  @HostBinding('attr.content-display-mode')
  contentDisplayMode: "full" | "wrapped" | "indented" = "full";

  @Input()
  public fill: boolean = false;

  private activeAnimationPlayer: any;

  get fillState() {
      return this.fill ? "on" : "off";
  }

  // 监听动画完成事件,记录活跃播放器
  onFillAnimationDone(event: AnimationEvent) {
    this.activeAnimationPlayer = event.player;
  }

  ngOnDestroy() {
    // 立即终止活跃动画,释放组件引用
    if (this.activeAnimationPlayer) {
      this.activeAnimationPlayer.destroy();
    }
  }
}

然后在组件模板中绑定动画的完成事件(假设动画绑定到根元素):

<div [@fillState]="fillState" (@fillState.done)="onFillAnimationDone($event)">
  <!-- 原有模板内容 -->
</div>

方案2:添加组件销毁时的动画过渡

修改fillState动画配置,添加针对组件销毁的:leave过渡,确保组件销毁时动画能正常收尾:

animations: [
  trigger('fillState', [
    state('on', style({ maxWidth: '2000px' })),
    state('off', style({ maxWidth: '768px' })),
    state('void', style({ maxWidth: '768px' })), // 组件销毁时的初始状态
    transition('on => off', animate('0.25s ease-in')),
    transition('off => on', animate('0.25s ease-out')),
    transition('* => void', animate('0.25s ease-in', style({ maxWidth: '768px' }))) // 销毁时的过渡
  ])
]

此方案让Angular在组件销毁时自动触发动画收尾,完成后正常清理组件。

方案3:路由守卫等待动画完成(临时 workaround)

如果上述方案无法立即生效,可通过路由守卫延迟导航,等待动画完成后再允许组件卸载:

  1. 创建守卫服务:
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable, timer } from 'rxjs';
import { ColumnComponent } from './column.component';

@Injectable({ providedIn: 'root' })
export class WaitForColumnAnimationGuard implements CanDeactivate<ColumnComponent> {
  canDeactivate(component: ColumnComponent): Observable<boolean> {
    if (component.fill) {
      component.fill = false; // 触发动画到off状态
      return timer(250); // 等待动画时长(与动画配置的0.25s一致)
    }
    return new Observable(observer => observer.next(true));
  }
}
  1. 更新路由配置:
{
  path: 'configuration/roles',
  loadChildren: () => import('gizmonic-features-angular').then(f => f.ConfigurationRolesModule),
  canDeactivate: [WaitForColumnAnimationGuard]
}

关键版本变化说明

Angular 12到18期间,动画模块的核心变化包括:

  • 动画性能优化,减少不必要的DOM操作,但对活跃动画的组件引用管理更严格
  • 组件销毁流程中,动画模块会等待未完成的动画,若动画状态未正确终止,会导致组件实例/DOM被意外保留
  • 独立组件支持带来的动画绑定逻辑调整(虽未使用独立组件,但底层动画模块实现有更新)

内容的提问来源于stack exchange,提问作者Joshua Miller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:13:17