Angular2+中CanDeactivate守卫二次回退跳两步,如何改为仅退一步?
解决Angular CanDeactivate守卫回退两次的问题
我之前也碰到过一模一样的情况!问题出在浏览器回退时的历史栈和Angular导航拦截的交互上:当你第一次点击回退按钮,浏览器会先把当前页面的历史记录更新(准备跳转到上一页),但CanDeactivate守卫拦截了这次导航,导致历史栈里留下了一个“无效”的跳转记录。第二次点击回退时,浏览器就会跳过当前页面,直接回退两步。
下面是两种经过验证的解决方案:
方案一:在守卫中手动控制回退逻辑
调整你的HomeGuard,通过Location服务来处理浏览器回退的情况,避免历史栈混乱:
import { Injectable } from '@angular/core'; import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, Location } from '@angular/router'; import { Observable } from 'rxjs'; import { HomeComponent } from './home.component'; @Injectable({ providedIn: 'root' }) export class HomeGuard implements CanDeactivate<HomeComponent> { constructor(private location: Location, private router: Router) {} canDeactivate( component: HomeComponent, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { // 判断是否需要拦截导航(比如有未保存更改) if (component.shouldPreventNavigation()) { // 特殊处理浏览器回退的情况:nextState为空或与当前路由相同 if (!nextState || nextState.url === currentState.url) { // 先阻止当前导航,再手动执行一次回退 setTimeout(() => { this.location.back(); }, 0); return false; } // 正常路由跳转(比如点击页面链接),弹出确认对话框 return component.confirmNavigation(); } return true; } }
对应的HomeComponent需要提供判断和确认方法:
import { Component } from '@angular/core'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent { hasUnsavedChanges = true; // 根据实际业务判断是否需要拦截 shouldPreventNavigation(): boolean { return this.hasUnsavedChanges; } confirmNavigation(): Promise<boolean> { return new Promise((resolve) => { const userConfirmed = confirm('你有未保存的更改,确定要离开吗?'); if (userConfirmed) { this.hasUnsavedChanges = false; // 确认后重置状态 resolve(true); } else { resolve(false); } }); } }
逻辑说明
- 当浏览器回退触发守卫时,
nextState会是空值或者和当前路由地址一致,这时候我们先返回false阻止默认导航,再用location.back()手动触发一次回退,确保历史栈只回退一步。 - 对于正常的路由跳转(比如点击页面内的链接),则正常弹出确认对话框,用户确认后允许导航并重置拦截状态。
方案二:监听导航事件修正历史栈
如果方案一不适合你的场景,还可以在HomeComponent中监听NavigationStart事件,手动修正历史栈:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, NavigationStart } from '@angular/router'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit, OnDestroy { private navSubscription: Subscription; hasUnsavedChanges = true; constructor(private router: Router) {} ngOnInit(): void { this.navSubscription = this.router.events.subscribe(event => { // 当有未保存更改且导航被拦截时,修正历史栈 if (event instanceof NavigationStart && this.hasUnsavedChanges) { if (event.url === this.router.url) { // 把当前路由重新推入历史栈,覆盖浏览器自动添加的无效记录 window.history.pushState(null, '', this.router.url); } } }); } ngOnDestroy(): void { this.navSubscription.unsubscribe(); } // 同样需要shouldPreventNavigation和confirmNavigation方法 shouldPreventNavigation(): boolean { return this.hasUnsavedChanges; } confirmNavigation(): Promise<boolean> { return new Promise((resolve) => { const userConfirmed = confirm('你有未保存的更改,确定要离开吗?'); if (userConfirmed) { this.hasUnsavedChanges = false; resolve(true); } else { resolve(false); } }); } }
逻辑说明
当守卫拦截导航后,浏览器已经将历史栈更新为上一页,我们通过window.history.pushState把当前路由重新推入历史栈,这样第二次点击回退时,就只会回退到上一页,而不会跳过当前页。
这两种方案都能解决第二次回退两步的问题,你可以根据自己的业务场景选择合适的方式。
内容的提问来源于stack exchange,提问作者Ravi Sevta
相关产品推荐
相关产品推荐

