Angular 2中首次调用GuardService时URL内Token不可用问题
问题分析与解决方案
你遇到的问题核心是异步操作的时序问题:StartpageService构造函数中订阅activatedRoute.queryParams是一个异步过程,而TokenGuard的canActivate方法会在这个订阅的回调函数执行前就调用validateToken,导致第一次调用时accessToken还没被赋值(所以控制台先输出undefined),第二次调用时订阅已经完成,token才被正确获取到。
下面给你两种可行的解决方案:
方案一:在validateToken中直接处理参数(推荐,更简洁)
修改StartpageService,去掉构造函数中的订阅,转而在validateToken方法里先获取query参数中的token,再发起API校验请求。利用switchMap操作符把两个异步操作串联起来,确保拿到token后再执行校验:
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Observable } from "rxjs/Observable"; import { ActivatedRoute, Router } from '@angular/router'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/of'; @Injectable() export class StartpageService { // 替换成你的实际API地址 private startpaginaUrl = '/api/startpage/validate'; constructor( private http: Http, private activatedRoute: ActivatedRoute, private router: Router ) { } public validateToken(): Observable<boolean> { // 先获取query参数里的token,再发起校验请求 return this.activatedRoute.queryParams .switchMap(params => { const accessToken = params['token']; // 如果没有token,直接返回false if (!accessToken) { return Observable.of(false); } // 有token则调用API校验 return this.http.get(`${this.startpaginaUrl}?token=${accessToken}`) .map(res => <boolean>res.json()); }); } }
方案二:用BehaviorSubject缓存token(适合多组件复用token的场景)
如果你的应用中多个地方需要用到这个token,可以用BehaviorSubject来缓存最新的token值,确保订阅它的地方能拿到最新的有效token:
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Observable } from "rxjs/Observable"; import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/take'; @Injectable() export class StartpageService { private startpaginaUrl = '/api/startpage/validate'; // 用BehaviorSubject保存token,初始值为null private accessTokenSubject = new BehaviorSubject<string>(null); constructor( private http: Http, private activatedRoute: ActivatedRoute, private router: Router ) { // 订阅queryParams,更新token的缓存 this.activatedRoute.queryParams.subscribe(params => { this.accessTokenSubject.next(params['token']); }); } public validateToken(): Observable<boolean> { return this.accessTokenSubject // 过滤掉初始的null值,等待token被赋值 .filter(token => token !== null) // 只取第一个有效token,避免重复触发校验 .take(1) .switchMap(token => { return this.http.get(`${this.startpaginaUrl}?token=${token}`) .map(res => <boolean>res.json()); }); } }
额外优化:Guard中处理校验失败的跳转
在TokenGuard中,建议添加校验失败后的跳转逻辑,比如跳转到登录页:
import {StartpageService} from './startpage.service'; import {Injectable} from '@angular/core'; import {CanActivate, Router,ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild} from '@angular/router'; import 'rxjs/add/operator/do'; @Injectable() export class TokenGuard implements CanActivate, CanActivateChild { constructor( private startPageService: StartpageService, private router: Router ) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { return this.startPageService.validateToken() .do(isValid => { if (!isValid) { // 替换成你的登录路由 this.router.navigate(['/login']); } }); } canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { return this.canActivate(route, state); } }
这样当token无效或不存在时,用户会被自动引导到登录页面,提升体验。
内容的提问来源于stack exchange,提问作者Bunnynut
相关产品推荐
相关产品推荐

