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)
如果上述方案无法立即生效,可通过路由守卫延迟导航,等待动画完成后再允许组件卸载:
- 创建守卫服务:
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)); } }
- 更新路由配置:
{ path: 'configuration/roles', loadChildren: () => import('gizmonic-features-angular').then(f => f.ConfigurationRolesModule), canDeactivate: [WaitForColumnAnimationGuard] }
关键版本变化说明
Angular 12到18期间,动画模块的核心变化包括:
- 动画性能优化,减少不必要的DOM操作,但对活跃动画的组件引用管理更严格
- 组件销毁流程中,动画模块会等待未完成的动画,若动画状态未正确终止,会导致组件实例/DOM被意外保留
- 独立组件支持带来的动画绑定逻辑调整(虽未使用独立组件,但底层动画模块实现有更新)
内容的提问来源于stack exchange,提问作者Joshua Miller
相关产品推荐
相关产品推荐

