Ionic 6:基于服务数据实现组件导航的正确方案(解决异步问题)
Ionic/Angular 登录状态校验优化方案
问题背景
我开发了一个Ionic应用,登录后将用户数据存储在Capacitor Storage中,核心登录代码如下:
this.httpService.login(this.username, this.password).subscribe(async (user: any) => { await Preferences.set({ key: 'user', value: JSON.stringify(user)}); this.router.navigate(['']); });
原本希望在首页判断用户是否已登录:未登录跳转至登录页,已登录则留在首页。但当前实现因Capacitor Preferences的异步特性导致判断不准确——首页执行校验时,服务中读取存储的操作还未完成。
当前UserService实现:
import { Injectable } from '@angular/core'; import { User } from '../models/user'; import { Preferences } from '@capacitor/preferences'; @Injectable({providedIn: 'root'}) export class UserService { private user: User = new User(); private userSet = false; constructor() { Preferences.get({ key: 'user' }).then((data: any) => { if(data) { const userObj = JSON.parse(data.value); this.user = Object.assign(this.user, userObj); this.userSet = true; } }); } hasUser() { return this.userSet; } }
首页校验逻辑:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { HttpService } from '../services/httpService'; import { UserService } from '../services/userService'; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'] }) export class HomePage implements OnInit { constructor(private httpService: HttpService, private userService: UserService, private router: Router) {} ngOnInit() { console.log(this.userService.hasUser()); if(!this.userService.hasUser()) { console.log('navigating'); this.router.navigate(['/login']);} } }
最优解决方案:Angular路由守卫
路由守卫是Angular官方推荐的路由访问控制方案,能在组件加载前完成异步校验,彻底解决异步时序问题,同时避免首页闪屏的用户体验问题。
1. 重构UserService,提供异步状态校验能力
import { Injectable } from '@angular/core'; import { User } from '../models/user'; import { Preferences } from '@capacitor/preferences'; import { Observable, from } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable({providedIn: 'root'}) export class UserService { private currentUser: User | null = null; // 异步获取登录状态,返回Observable适配路由守卫 getUserStatus(): Observable<boolean> { // 优先使用缓存,避免重复读取存储 if (this.currentUser) { return new Observable(observer => observer.next(true)); } // 从Capacitor Storage读取用户信息 return from(Preferences.get({ key: 'user' })).pipe( map(data => { if (data?.value) { this.currentUser = JSON.parse(data.value) as User; return true; } return false; }) ); } // 登录成功后更新缓存与存储 setUser(user: User): void { this.currentUser = user; Preferences.set({ key: 'user', value: JSON.stringify(user) }); } // 登出时清除缓存与存储 clearUser(): void { this.currentUser = null; Preferences.remove({ key: 'user' }); } }
2. 创建AuthGuard路由守卫
import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { UserService } from './user.service'; import { Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; @Injectable({providedIn: 'root'}) export class AuthGuard implements CanActivate { constructor(private userService: UserService, private router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> { return this.userService.getUserStatus().pipe( map(isLoggedIn => { // 已登录允许访问目标路由 if (isLoggedIn) { return true; } // 未登录跳转至登录页 this.router.navigate(['/login']); return false; }) ); } }
3. 配置路由,给首页添加守卫
在app-routing.module.ts中为首页路由绑定AuthGuard:
import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; import { AuthGuard } from './services/auth.guard'; const routes: Routes = [ { path: '', loadChildren: () => import('./home/home.module').then(m => m.HomePageModule), canActivate: [AuthGuard] // 只有登录用户才能访问首页 }, { path: 'login', loadChildren: () => import('./login/login.module').then(m => m.LoginPageModule) }, // 其他路由配置... ]; @NgModule({ imports: [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) ], exports: [RouterModule] }) export class AppRoutingModule {}
4. 更新登录逻辑
登录成功后调用UserService的setUser方法更新缓存:
this.httpService.login(this.username, this.password).subscribe((user: any) => { this.userService.setUser(user); this.router.navigate(['']); });
临时替代方案:首页等待异步操作完成
如果暂时不想引入路由守卫,可以通过等待UserService初始化完成来解决时序问题,但此方案会导致首页先加载再跳转,用户体验较差。
重构UserService,暴露初始化Promise
import { Injectable } from '@angular/core'; import { User } from '../models/user'; import { Preferences } from '@capacitor/preferences'; @Injectable({providedIn: 'root'}) export class UserService { private user: User | null = null; private initPromise: Promise<void>; constructor() { // 保存初始化时读取存储的Promise this.initPromise = Preferences.get({ key: 'user' }).then((data: any) => { if(data?.value) { this.user = JSON.parse(data.value) as User; } }); } // 等待初始化完成后返回登录状态 async hasUser(): Promise<boolean> { await this.initPromise; return this.user !== null; } }
更新首页校验逻辑
async ngOnInit() { const isLoggedIn = await this.userService.hasUser(); if(!isLoggedIn) { this.router.navigate(['/login']); } }
方案对比
- 路由守卫方案:Angular标准实践,组件加载前完成校验,无页面闪屏,可统一管理所有需登录的路由,扩展性强。
- 首页等待方案:实现简单,但存在页面闪屏问题,仅适合小型应用或临时测试场景。
内容的提问来源于stack exchange,提问作者plasmy
相关产品推荐
相关产品推荐

