Angular5登录跳转页面无数据,仅F5刷新才加载的问题排查
问题原因分析
你猜的没错!问题核心就是httpOptions这个常量的初始化时机导致的。
当Angular启动并注入你的UserService时,httpOptions就会被创建,这时候你还没登录,localStorage.getItem('token')拿到的是空值,所以Authorization头其实是无效的空值/Bearer null。
登录成功后跳转到Welcome页面时,UserService已经初始化完成了,httpOptions不会重新生成,所以请求时还是带着那个无效的空token头,API自然返回不了数据。而F5刷新时,整个应用重新加载,UserService会重新初始化,这时候localStorage里已经有登录成功后存入的token了,httpOptions里的Authorization头就正确了,所以请求能正常拿到数据。
解决方案
推荐两种方案,第二种是Angular的最佳实践,更适合长期维护:
方案1:每次请求动态生成请求头
修改UserService的getUsers方法,每次发起请求时才去获取最新的token并生成请求头:
@Injectable() export class UserService { baseUrl = environment.apiUrl; constructor(private httpClient: HttpClient) { } getUsers (): Observable<User[]> { // 每次请求时动态获取当前token const currentToken = localStorage.getItem('token'); const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': currentToken ? `Bearer ${currentToken}` : '' }) }; return this.httpClient.get<User[]>(this.baseUrl + 'users', httpOptions).catch(this.handleError); } }
方案2:使用HttpInterceptor全局注入认证头(推荐)
这种方式不用在每个服务里手动处理请求头,所有需要认证的请求都会自动带上token,更优雅也更易维护。
第一步:创建认证拦截器
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 每次请求时获取最新的token const token = localStorage.getItem('token'); if (token) { // 克隆请求并添加Authorization头(因为HttpRequest是不可变的) const authenticatedReq = req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) }); return next.handle(authenticatedReq); } // 没有token就直接放行原请求 return next.handle(req); } }
第二步:在AppModule中注册拦截器
import { NgModule } from '@angular/core'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './path/to/auth.interceptor'; // 替换成你的拦截器路径 @NgModule({ // ...其他模块配置(declarations、imports等) providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true // 允许多个拦截器并存 } ] }) export class AppModule { }
第三步:简化UserService代码
现在可以删掉原来的httpOptions定义,请求会自动带上认证头:
@Injectable() export class UserService { baseUrl = environment.apiUrl; constructor(private httpClient: HttpClient) { } getUsers (): Observable<User[]> { return this.httpClient.get<User[]>(this.baseUrl + 'users').catch(this.handleError); } }
关于官方示例的说明
官方示例里用常量定义httpOptions,通常是在应用启动时就已经有可用token的场景(比如从后端获取的初始认证信息),而你的场景是登录后才生成并存储token,所以不能直接照搬那种写法。
内容的提问来源于stack exchange,提问作者Sweetie
相关产品推荐
相关产品推荐

