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

Angular Router:如何在routerLink触发时立即销毁旧组件?

Angular路由切换时立即销毁旧组件的实现方案

一、核心原理说明

Angular默认路由切换时,旧组件的销毁与新组件的初始化是并行执行的,但可以通过路由动画时序控制、路由守卫/Resolver或手动路由流程控制调整顺序,实现旧组件先销毁、再显示加载Spinner、最后加载新组件的效果。

二、具体实现方法

1. 利用路由动画强制旧组件先销毁(适配路由动画需求)

通过定义序列动画(sequence),让旧组件的离开动画先执行完成,再触发新组件加载,同时在旧组件销毁后显示Spinner。

示例代码:

// app.component.ts
import { trigger, transition, sequence, style, animate, query } from '@angular/animations';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';

@Component({
  selector: 'app-root',
  animations: [
    trigger('routeAnimations', [
      transition('* => *', [
        sequence([
          // 执行旧组件离开动画,完成后自动销毁
          query(':leave', [
            style({ opacity: 1 }),
            animate('300ms ease-out', style({ opacity: 0 }))
          ], { optional: true }),
          // 新组件加载前显示Spinner
          query('app-spinner', [
            style({ opacity: 0 }),
            animate('200ms ease-in', style({ opacity: 1 }))
          ], { optional: true }),
          // 新组件加载完成后执行进入动画
          query(':enter', [
            style({ opacity: 0 }),
            animate('300ms ease-in', style({ opacity: 1 }))
          ], { optional: true }),
          // 隐藏Spinner
          query('app-spinner', [
            animate('200ms ease-out', style({ opacity: 0 }))
          ], { optional: true })
        ])
      ])
    ]),
  ],
  template: `
    <div [@routeAnimations]="prepareRoute(outlet)">
      <router-outlet #outlet="outlet"></router-outlet>
      <app-spinner class="spinner-overlay"></app-spinner>
    </div>
  `
})
export class AppComponent {
  constructor(private router: Router) {}

  prepareRoute(outlet: RouterOutlet) {
    return outlet?.activatedRouteData?.['animation'] || '';
  }
}

在旧组件的销毁钩子中同步状态:

// 旧组件.ts
import { SpinnerService } from '../spinner.service';

constructor(private spinnerService: SpinnerService) {}

ngOnDestroy() {
  this.spinnerService.show();
}

2. 结合路由守卫与Resolver控制流程

通过CanDeactivate守卫触发旧组件销毁与Spinner显示,再用Resolver处理新组件/懒加载模块的加载逻辑,完成后隐藏Spinner。

  • 实现CanDeactivate守卫:
// can-deactivate.guard.ts
import { CanDeactivate } from '@angular/router';
import { Injectable } from '@angular/core';
import { SpinnerService } from './spinner.service';

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<any> {
  constructor(private spinnerService: SpinnerService) {}

  canDeactivate(component: any) {
    this.spinnerService.show();
    return true; // 允许路由切换,此时旧组件会被销毁
  }
}
  • 配置路由(含懒加载模块):
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CanDeactivateGuard } from './can-deactivate.guard';
import { NewModuleResolver } from './new-module.resolver';

const routes: Routes = [
  { path: 'old', component: OldComponent },
  {
    path: 'new',
    loadChildren: () => import('./new/new.module').then(m => m.NewModule),
    canDeactivate: [CanDeactivateGuard],
    resolve: { moduleData: NewModuleResolver }
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [CanDeactivateGuard, NewModuleResolver]
})
export class AppRoutingModule {}
  • Resolver处理加载逻辑:
// new-module.resolver.ts
import { Resolve } from '@angular/router';
import { Injectable } from '@angular/core';
import { SpinnerService } from './spinner.service';

@Injectable()
export class NewModuleResolver implements Resolve<any> {
  constructor(private spinnerService: SpinnerService) {}

  resolve() {
    // 模拟懒加载模块的加载等待
    return new Promise(resolve => {
      setTimeout(() => {
        this.spinnerService.hide();
        resolve({ data: 'loaded' });
      }, 1200);
    });
  }
}

3. 手动控制路由切换流程(灵活适配特殊场景)

不直接使用routerLink,通过点击事件手动控制组件显示与路由导航:

<!-- 模板 -->
<button (click)="navigateToNew()">跳转至新页面</button>

<router-outlet *ngIf="!showSpinner"></router-outlet>
<app-spinner *ngIf="showSpinner" class="spinner-overlay"></app-spinner>
// 组件逻辑
import { Router } from '@angular/router';

constructor(private router: Router) {}

showSpinner = false;

navigateToNew() {
  // 1. 隐藏旧组件,显示Spinner
  this.showSpinner = true;
  // 2. 执行路由导航
  this.router.navigate(['/new']).then(() => {
    // 3. 导航完成后隐藏Spinner
    this.showSpinner = false;
  });
}

三、注意事项

  • 路由动画场景下,必须确保动画时序为旧组件离开→显示Spinner→新组件进入,避免并行导致Spinner覆盖旧组件。
  • 懒加载模块时,可结合NavigationStart和NavigationEnd事件,确保模块加载期间Spinner持续显示。
  • 建议用RxJS的BehaviorSubject实现Spinner状态的跨组件同步,避免状态不一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:40:01