我已经使用常见的“我可以离开页面”对话框实现了一个 canDeactivate 守卫。在大多数情况下,它都有效。如果我确认,我可以留下一个页面,如果我取消,我可以留下。这里是警卫:
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanDeactivate, Router } from '@angular/router';
import { ModalComponent } from '@ccp/app/controls/layout/modal/modal.component';
import { Observable } from 'rxjs';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
}
@Injectable({
providedIn: 'root'
})
export class CanLeaveGuard implements CanDeactivate<CanComponentDeactivate> {
constructor(private router: Router) { }
canDeactivate(
component: CanComponentDeactivate,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// the nextState is an optional url state that allows us to see the requested url
// we can use this say for example to detect if the user want to route to a previous
// page, if he does we can avoid displaying the poppin by conditionally returning true.
return component.canDeactivate ? component.canDeactivate() : true;
}
}
这里是当守卫调用指定组件中的方法时调用的canDeactivate方法
public shouldUserLeavePreRegFlow(dialogComponent: ModalComponent, decision: Subject<boolean>, router: Router): Observable<boolean | UrlTree> | boolean | UrlTree {
dialogComponent.open();
return decision
.pipe(map((isLeavingPage) => {
if (isLeavingPage) {
// leave page
console.log('leave page true');
return true;
} else {
// stay on current page
return router.createUrlTree([router.routerState.snapshot.url]);
}
}));
}
决策变量是您在下面看到的主题 navigateAwaySelection$,当它弹出并等待用户响应时,它会从我的自定义对话中接受真或假的发射,当他响应时调用接受方法,该方法调用接下来就通过用户判断真假的主题。
public acceptance(decision: boolean): void { this.navigateAwaySelection$.next(decision); }
因此,这在导航到典型视图时有效。但是有一种极端情况,当我们想确认离开页面时,我们不得不在弹出的对话框上单击两次。这种情况是当我们尝试访问由 canActivate 保护的页面时,如果在桌面上,该保护将重定向到第二个页面,在这种情况下,我们运行 navigateByUrl 进行重定向。
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean | UrlTree {
if (this.deviceDetectorService.isDesktop()) {
this.router.navigateByUrl('register/manual',
{queryParams: route.queryParams}
);
return false;
}
return true;
}
}
导航跟踪器显示我们进入注册/手册(预期页面)页面,但实际上屏幕快速闪烁,我们回到了对话和原始页面,再次点击离开页面终于让我们到达目的地。就像第一次导航被取消只是第二次被接受一样,请记住 canDeactivate 保护是在其自己的功能模块文件夹中的指定组件路由中声明的,因为我正在使用延迟加载的模块功能,如果我尝试添加它到全局路由列表,canDeactivate 防护中的“组件”变为空。有关这方面的更多信息,请参阅https://github.com/angular/angular/issues/16868。
canDeactivate guard > canActivate guard > canDeactivate guard > canActivate guard > 到达预期页面。
有没有办法解决这个问题,这样我只需要在离开页面上单击一次,就像在其他页面中一样?
角度示踪剂
// 单击链接以导航离开时的跟踪器结果
Router Event: NavigationStart
browser_adapter.ts:41 NavigationStart(id: 4, url: '/')
browser_adapter.ts:41 NavigationStart {id: 4, url: "/", navigationTrigger: "imperative", restoredState: null}
browser_adapter.ts:47 Router Event: RouteConfigLoadStart
browser_adapter.ts:41 RouteConfigLoadStart(path: register)
browser_adapter.ts:41 RouteConfigLoadStart {route: {…}}
logger.ts:37 Load chunk started register
browser_adapter.ts:47 Router Event: RouteConfigLoadEnd
browser_adapter.ts:41 RouteConfigLoadEnd(path: register)
browser_adapter.ts:41 RouteConfigLoadEnd {route: {…}}
logger.ts:37 Load chunk ended register
browser_adapter.ts:47 Router Event: RoutesRecognized
browser_adapter.ts:41 RoutesRecognized(id: 4, url: '/', urlAfterRedirects: '/register', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register', path:'register') { Route(url:'', path:'') } } } )
browser_adapter.ts:41 RoutesRecognized {id: 4, url: "/", urlAfterRedirects: "/register", state: RouterStateSnapshot}
browser_adapter.ts:47 Router Event: GuardsCheckStart
browser_adapter.ts:41 GuardsCheckStart(id: 4, url: '/', urlAfterRedirects: '/register', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register', path:'register') { Route(url:'', path:'') } } } )
browser_adapter.ts:41 GuardsCheckStart {id: 4, url: "/", urlAfterRedirects: "/register", state: RouterStateSnapshot}
//点击离开一次后
Router Event: ChildActivationStart
browser_adapter.ts:41 ChildActivationStart(path: '')
browser_adapter.ts:41 ChildActivationStart {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ActivationStart
browser_adapter.ts:41 ActivationStart(path: 'register')
browser_adapter.ts:41 ActivationStart {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: NavigationCancel
browser_adapter.ts:41 NavigationCancel(id: 4, url: '/')
browser_adapter.ts:41 NavigationCancel {id: 4, url: "/", reason: "Navigation ID 4 is not equal to the current navigation id 5"}
browser_adapter.ts:47 Router Event: NavigationStart
browser_adapter.ts:41 NavigationStart(id: 5, url: '/register/manual')
browser_adapter.ts:41 NavigationStart {id: 5, url: "/register/manual", navigationTrigger: "imperative", restoredState: null}
browser_adapter.ts:47 Router Event: RoutesRecognized
browser_adapter.ts:41 RoutesRecognized(id: 5, url: '/register/manual', urlAfterRedirects: '/register/manual', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register/manual', path:'register/manual') { Route(url:'', path:'') } } } )
browser_adapter.ts:41 RoutesRecognized {id: 5, url: "/register/manual", urlAfterRedirects: "/register/manual", state: RouterStateSnapshot}
browser_adapter.ts:47 Router Event: GuardsCheckStart
browser_adapter.ts:41 GuardsCheckStart(id: 5, url: '/register/manual', urlAfterRedirects: '/register/manual', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register/manual', path:'register/manual') { Route(url:'', path:'') } } } )
browser_adapter.ts:41 GuardsCheckStart {id: 5, url: "/register/manual", urlAfterRedirects: "/register/manual", state: RouterStateSnapshot}
第二次点击离开后
Router Event: ChildActivationStart
browser_adapter.ts:41 ChildActivationStart(path: '')
browser_adapter.ts:41 ChildActivationStart {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ActivationStart
browser_adapter.ts:41 ActivationStart(path: 'register/manual')
browser_adapter.ts:41 ActivationStart {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ChildActivationStart
browser_adapter.ts:41 ChildActivationStart(path: 'register/manual')
browser_adapter.ts:41 ChildActivationStart {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ActivationStart
browser_adapter.ts:41 ActivationStart(path: '')
browser_adapter.ts:41 ActivationStart {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: GuardsCheckEnd
browser_adapter.ts:41 GuardsCheckEnd(id: 5, url: '/register/manual', urlAfterRedirects: '/register/manual', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register/manual', path:'register/manual') { Route(url:'', path:'') } } } , shouldActivate: true)
browser_adapter.ts:41 GuardsCheckEnd {id: 5, url: "/register/manual", urlAfterRedirects: "/register/manual", state: RouterStateSnapshot, shouldActivate: true}
browser_adapter.ts:47 Router Event: ResolveStart
browser_adapter.ts:41 ResolveStart(id: 5, url: '/register/manual', urlAfterRedirects: '/register/manual', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register/manual', path:'register/manual') { Route(url:'', path:'') } } } )
browser_adapter.ts:41 ResolveStart {id: 5, url: "/register/manual", urlAfterRedirects: "/register/manual", state: RouterStateSnapshot}
browser_adapter.ts:47 Router Event: ResolveEnd
browser_adapter.ts:41 ResolveEnd(id: 5, url: '/register/manual', urlAfterRedirects: '/register/manual', state: Route(url:'', path:'') { Route(url:'', path:'') { Route(url:'register/manual', path:'register/manual') { Route(url:'', path:'') } } } )
browser_adapter.ts:41 ResolveEnd {id: 5, url: "/register/manual", urlAfterRedirects: "/register/manual", state: RouterStateSnapshot}
browser_adapter.ts:47 Router Event: ActivationEnd
browser_adapter.ts:41 ActivationEnd(path: '')
browser_adapter.ts:41 ActivationEnd {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ChildActivationEnd
browser_adapter.ts:41 ChildActivationEnd(path: 'register/manual')
browser_adapter.ts:41 ChildActivationEnd {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ActivationEnd
browser_adapter.ts:41 ActivationEnd(path: 'register/manual')
browser_adapter.ts:41 ActivationEnd {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ChildActivationEnd
browser_adapter.ts:41 ChildActivationEnd(path: '')
browser_adapter.ts:41 ChildActivationEnd {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ActivationEnd
browser_adapter.ts:41 ActivationEnd(path: '')
browser_adapter.ts:41 ActivationEnd {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: ChildActivationEnd
browser_adapter.ts:41 ChildActivationEnd(path: '')
browser_adapter.ts:41 ChildActivationEnd {snapshot: ActivatedRouteSnapshot}
browser_adapter.ts:47 Router Event: NavigationEnd
browser_adapter.ts:41 NavigationEnd(id: 5, url: '/register/manual', urlAfterRedirects: '/register/manual')
browser_adapter.ts:41 NavigationEnd {id: 5, url: "/register/manual", urlAfterRedirects: "/register/manual"}
也许这是罪魁祸首:
browser_adapter.ts:47 Router Event: NavigationCancel
browser_adapter.ts:41 NavigationCancel(id: 4, url: '/')
browser_adapter.ts:41 NavigationCancel {id: 4, url: "/", reason: "Navigation ID 4 is not equal to the current navigation id 5"}