APP_INITIALIZER中Router无法获取有效路径的问题排查与解决
我在站点中使用APP_INITIALIZER实现OAuth2自动认证:用户进入站点时,若未认证则重定向到Identity Server,并携带用户当前访问的子页面信息;登录后Identity Server会跳转回网站,我可据此直接展示用户原本想要访问的子页面。但问题是,在APP_INITIALIZER中获取router.url时,始终返回'/',而非用户实际输入的路径(如localhost/page1/subpage2)。
将该代码移至组件的ngOnInit中测试时,router.url能正确返回真实路径,可正常传递给Identity Server,但此方式仅适用于组件层面,无法覆盖整个应用,因此我希望在APP_INITIALIZER中处理。
我使用的是Angular 15.1版本及@angular/router,应用采用独立组件。已尝试移除withEnabledBlockingInitialNavigation(),但无效果。在effects及APP_INITIALIZER中,router.url均始终返回'/';而在组件ngOnInit中触发认证流程时,effects里的router.url能正确获取真实路径。
请问这是Bug还是Router在APP_INITIALIZER中未完全可用的设计?如何解决在APP_INITIALIZER中获取用户真实访问路径,以便重定向到外部登录页时传递该数据?
相关代码
main.ts
bootstrapApplication(AppComponent, { providers: [ provideStore(appReducers), provideEffects(AuthEffects), provideRouter(routes, withPreloading(PreloadAllModules), withEnabledBlockingInitialNavigation() ), provideRouterStore(), importProvidersFrom(BrowserAnimationsModule), provideHttpClient(), provideOAuthClient(), { provide: APP_INITIALIZER, useFactory: authAppInitializerFactory, multi: true, deps: [Store, Router], } ], }).catch((err) => console.error('Last error catch', { err }));
auth-app-Initializer.factory.ts
export function authAppInitializerFactory(store: Store, router: Router): () => Promise<void> { return () => new Promise((resolve) => { console.log('URL in initializer' + router.url); // here router.url always return '/' :( store .select(selectIsAuthenticationProcessEnded) .pipe( takeWhile((isAuthenticationProcessEnded) => !isAuthenticationProcessEnded, true), filter((x) => x) ) .subscribe(() => resolve()); store.dispatch(initiateLoginSequence()); }); }
问题原因
这不是Angular的Bug,而是路由初始化时机的设计问题:APP_INITIALIZER的执行是在应用初始化早期,此时路由器还没完成对当前URL的解析和导航流程,所以router.url停留在默认的'/'。只有当路由器完成初始导航后,router.url才会更新为用户实际访问的路径。
解决方法
方法1:等待路由完成初始导航后获取路径
修改初始化工厂函数,监听NavigationEnd事件,等路由导航完成后再获取真实路径:
export function authAppInitializerFactory(store: Store, router: Router): () => Promise<void> { return () => new Promise((resolve) => { // 监听首次导航完成事件 router.events.pipe( filter(event => event instanceof NavigationEnd), take(1) ).subscribe((event: NavigationEnd) => { const actualUrl = event.urlAfterRedirects; // 获取重定向后的最终路径 console.log('真实访问路径:', actualUrl); // 将路径存入store,供登录流程使用 store.dispatch(setRedirectUrl({ url: actualUrl })); store.dispatch(initiateLoginSequence()); }); store .select(selectIsAuthenticationProcessEnded) .pipe( takeWhile((isAuthenticationProcessEnded) => !isAuthenticationProcessEnded, true), filter((x) => x) ) .subscribe(() => resolve()); }); }
方法2:直接读取浏览器原生location对象
如果不需要处理路由重定向,直接用浏览器原生API获取用户输入的原始路径:
export function authAppInitializerFactory(store: Store, router: Router): () => Promise<void> { return () => new Promise((resolve) => { const actualUrl = window.location.pathname + window.location.search; console.log('真实访问路径:', actualUrl); store.dispatch(setRedirectUrl({ url: actualUrl })); store.dispatch(initiateLoginSequence()); store .select(selectIsAuthenticationProcessEnded) .pipe( takeWhile((isAuthenticationProcessEnded) => !isAuthenticationProcessEnded, true), filter((x) => x) ) .subscribe(() => resolve()); }); }
方法3:通过NgRx Router Store获取路径(已集成NgRx的场景)
如果项目用了NgRx Router Store,可直接从路由状态中读取路径:
export function authAppInitializerFactory(store: Store, router: Router): () => Promise<void> { return () => new Promise((resolve) => { store.select(selectRouterState).pipe( filter(state => state != null), take(1) ).subscribe(routerState => { const actualUrl = routerState.state.url; console.log('真实访问路径:', actualUrl); store.dispatch(setRedirectUrl({ url: actualUrl })); store.dispatch(initiateLoginSequence()); }); store .select(selectIsAuthenticationProcessEnded) .pipe( takeWhile((isAuthenticationProcessEnded) => !isAuthenticationProcessEnded, true), filter((x) => x) ) .subscribe(() => resolve()); }); }
注意事项
- 方法1适合需要处理路由重定向后的最终路径的场景;
- 方法2更直接,无需依赖Angular路由初始化,适合简单场景;
- 登录成功后,记得从store中取出保存的重定向URL,导航到该页面。
内容的提问来源于stack exchange,提问作者UserLuke

