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

Angular 5中CanDeactivate仅触发一次及自定义PrimeNG模态框问题

嘿,我来帮你搞定Angular 5里CanDeactivate守卫只触发一次的问题,结合你用PrimeNG p-dialog的场景,咱们一步步理清楚:

问题根源分析

为啥守卫只触发一次呢?大概率是因为你返回的Observable/Promise没有在每次导航时重新创建,或者模态框的状态没重置到位,导致后续导航时守卫直接复用了之前的结果,没重新走判断逻辑。比如第一次弹出模态框后,Observable没有正确完成或者状态没有重置,下次导航就不会再触发新的判断流程了。

解决方案

我们需要确保每次触发CanDeactivate时,都生成新的Observable来处理模态框的确认/取消操作,同时正确管理模态框的显示状态和回调逻辑。

1. 重构守卫服务代码

修改你的DeactivateUnsavedChangesService,保证每次canDeactivate调用都返回全新的Observable,并且通过Subject来接收模态框的操作结果:

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, Subject } from 'rxjs';
// 替换成你实际的组件类型,方便类型校验
import { TestPageComponent } from './test-page.component'; 

@Injectable()
export class DeactivateUnsavedChangesService implements CanDeactivate<TestPageComponent> {
  canDeactivate(
    component: TestPageComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    // 先判断组件是否存在未保存的测试状态(这里假设组件有对应的判断方法)
    if (!component.hasUnsavedTestChanges()) {
      return true; // 没有未保存内容,直接允许导航
    }

    // 每次都创建新的Subject,避免复用旧的Observable
    const confirmSubject = new Subject<boolean>();
    
    // 调用组件的方法打开模态框,并传入回调函数接收结果
    component.openUnsavedConfirmDialog((result: boolean) => {
      confirmSubject.next(result);
      confirmSubject.complete(); // 完成Observable,防止内存泄漏
    });

    return confirmSubject.asObservable();
  }
}

2. 组件内处理模态框逻辑

在你的测试页面组件里,实现打开模态框的方法,并且绑定按钮的点击事件来触发回调:

import { Component } from '@angular/core';

@Component({
  selector: 'app-test-page',
  templateUrl: './test-page.component.html',
  styleUrls: ['./test-page.component.css']
})
export class TestPageComponent {
  isConfirmDialogVisible = false;
  private dialogCallback: (result: boolean) => void;

  // 业务逻辑:判断是否有未保存的测试内容
  hasUnsavedTestChanges(): boolean {
    // 这里替换成你的实际判断逻辑,比如测试已启动且未保存
    return this.testIsStarted && !this.testIsSaved;
  }

  // 打开模态框,接收守卫传来的回调
  openUnsavedConfirmDialog(callback: (result: boolean) => void): void {
    this.dialogCallback = callback;
    this.isConfirmDialogVisible = true;
  }

  // 确认离开的按钮事件
  onConfirmLeave(): void {
    this.isConfirmDialogVisible = false;
    this.dialogCallback(true); // 告诉守卫允许导航
  }

  // 取消离开的按钮事件
  onCancelLeave(): void {
    this.isConfirmDialogVisible = false;
    this.dialogCallback(false); // 告诉守卫阻止导航
  }
}

3. 模板中配置PrimeNG的p-dialog

在组件的HTML模板里,绑定模态框的显示状态和按钮事件:

<p-dialog 
  header="未保存提示" 
  [(visible)]="isConfirmDialogVisible" 
  [modal]="true" 
  [closable]="false"
  [resizable]="false">
  <p>您的测试尚未保存,确定要离开当前页面吗?</p>
  <p-footer>
    <button pButton type="button" label="取消" (click)="onCancelLeave()"></button>
    <button pButton type="button" label="确定离开" class="p-button-danger" (click)="onConfirmLeave()"></button>
  </p-footer>
</p-dialog>

4. 路由中正确配置守卫

确保守卫服务被正确注册到路由和模块中:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TestPageComponent } from './test-page.component';
import { DeactivateUnsavedChangesService } from './deactivate-unsaved-changes.service';

const routes: Routes = [
  {
    path: 'test',
    component: TestPageComponent,
    canDeactivate: [DeactivateUnsavedChangesService]
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
  providers: [DeactivateUnsavedChangesService] // 确保守卫被注入
})
export class TestRoutingModule { }
关键注意事项
  • 每次canDeactivate调用都要创建新的Subject,绝对不能复用同一个Observable,否则第一次触发后后续导航会直接用旧结果。
  • 模态框关闭后一定要调用confirmSubject.complete(),避免内存泄漏。
  • 组件里的dialogCallback要在每次打开模态框时重新赋值,确保每次导航的回调都是最新的。

这样修改后,每次用户尝试导航离开时,都会触发守卫并弹出模态框,不会再出现只触发一次的问题啦~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:59:01