Angular 21项目Redux与HTTP拦截器集成异常:JWT同步问题及存储整合需求
Hey there, let's tackle your Angular 21 Redux and HTTP interceptor synchronization issues, along with the type errors and storage integration problems in your code. I'll break this down step by step to get everything working as expected.
1. Fix Type Errors in Your Auth Code
First, let's eliminate those implicit any type warnings and variable initialization issues. Type safety will make your code more reliable and easier to debug.
Update JWT Service Functions
Add explicit types and null checks to your JWT storage helpers:
import { User } from "../model/User"; // Get access token from localStorage with type safety const getLocalAccessToken = (): string | null => { const userStr = localStorage.getItem("user"); if (!userStr) return null; const user: User = JSON.parse(userStr); return user.accessToken ?? null; }; // Get refresh token from localStorage const getLocalRefreshToken = (): string | null => { const userStr = localStorage.getItem("user"); if (!userStr) return null; const user: User = JSON.parse(userStr); return user.refreshToken ?? null; }; // Update access token in localStorage const updateLocalAccessToken = (token: string): void => { const userStr = localStorage.getItem("user"); if (!userStr) return; const user: User = JSON.parse(userStr); user.accessToken = token; localStorage.setItem("user", JSON.stringify(user)); }; // Clear user data from localStorage const removeUser = (): void => { localStorage.removeItem("user"); };
Fix the Login Function's Type Issues
Add typed interfaces for credentials and API responses, and use Redux Thunk to sync state after login:
import { Dispatch } from '@reduxjs/toolkit'; import { loginSuccess } from './authSlice'; import axios from 'axios'; // Define your API client (adjust baseURL to your backend) const api = axios.create({ baseURL: '/api' }); // Typed interfaces for type safety interface LoginCredentials { username: string; password: string; } interface AuthResponse extends User { accessToken: string; refreshToken: string; } // Login function with Redux sync const login = (credentials: LoginCredentials) => { return (dispatch: Dispatch) => { return api.post<AuthResponse>("/auth/signin", credentials) .then(response => { const user = response.data; dispatch(loginSuccess(user)); // Sync user data to Redux return user; }); }; };
2. Sync Redux Store with LocalStorage
The core issue here is that your Redux state and localStorage weren't staying in sync. Let's fix this with Redux Toolkit (the recommended way to use Redux in modern apps) by adding automatic localStorage sync in your reducer.
Create a Redux Auth Slice
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { User } from '../model/User'; interface AuthState { isLoggedIn: boolean; user: User | null; accessToken: string | null; refreshToken: string | null; } // Initialize state from localStorage const storedUserStr = localStorage.getItem("user"); const initialState: AuthState = storedUserStr ? { isLoggedIn: true, user: JSON.parse(storedUserStr) as User, accessToken: (JSON.parse(storedUserStr) as User).accessToken, refreshToken: (JSON.parse(storedUserStr) as User).refreshToken, } : { isLoggedIn: false, user: null, accessToken: null, refreshToken: null, }; const authSlice = createSlice({ name: 'auth', initialState, reducers: { loginSuccess: (state, action: PayloadAction<User>) => { state.isLoggedIn = true; state.user = action.payload; state.accessToken = action.payload.accessToken; state.refreshToken = action.payload.refreshToken; // Sync to localStorage whenever state updates localStorage.setItem("user", JSON.stringify(action.payload)); }, logoutSuccess: (state) => { state.isLoggedIn = false; state.user = null; state.accessToken = null; state.refreshToken = null; localStorage.removeItem("user"); }, refreshTokenSuccess: (state, action: PayloadAction<string>) => { state.accessToken = action.payload; // Update localStorage with new access token if (state.user) { state.user.accessToken = action.payload; localStorage.setItem("user", JSON.stringify(state.user)); } } } }); export const { loginSuccess, logoutSuccess, refreshTokenSuccess } = authSlice.actions; export default authSlice.reducer;
Configure Your Redux Store
Set up the store with your auth reducer:
import { configureStore } from '@reduxjs/toolkit'; import authReducer from './authSlice'; export const store = configureStore({ reducer: { auth: authReducer } }); // Export types for component usage export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;
3. Build a Sync'd HTTP Interceptor
Now let's create an Angular HTTP interceptor that pulls the latest token from Redux (not just localStorage) to ensure every request uses the most up-to-date auth data.
Create the Auth Interceptor
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { RootState } from './store'; import { selectAccessToken } from './authSelectors'; import { take, switchMap, catchError } from 'rxjs/operators'; import { refreshToken, logoutSuccess } from './authSlice'; import { of } from 'rxjs'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private store: Store<RootState>) {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { return this.store.select(selectAccessToken).pipe( take(1), // Grab the latest token from Redux map(token => { // Add Authorization header if token exists return token ? request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : request; }), switchMap(modifiedRequest => next.handle(modifiedRequest).pipe( catchError(error => { // Handle 401 errors by refreshing the token if (error.status === 401 && !request.url.includes('/auth/refreshtoken')) { return this.store.dispatch(refreshToken()).pipe( switchMap(newToken => { // Retry the request with the new token const retryRequest = request.clone({ setHeaders: { Authorization: `Bearer ${newToken}` } }); return next.handle(retryRequest); }), catchError(() => { // Refresh failed: log user out this.store.dispatch(logoutSuccess()); // Add your login page redirect logic here (e.g., this.router.navigate(['/login'])) return of(error); }) ); } return of(error); }) )) ); } }
Create a Redux Selector for Access Token
Add a selector to cleanly access the token from state:
import { createSelector } from '@reduxjs/toolkit'; import { RootState } from './store'; const selectAuthState = (state: RootState) => state.auth; export const selectAccessToken = createSelector( selectAuthState, authState => authState.accessToken );
Register the Interceptor in Angular
Add the interceptor to your app module to enable it globally:
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './auth.interceptor'; import { store } from './store'; import { Store } from '@ngrx/store'; @NgModule({ providers: [ { provide: Store, useValue: store }, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class AppModule { }
4. Add Token Refresh Logic
Finally, add a thunk action to handle token refresh and sync with Redux:
import { Dispatch } from '@reduxjs/toolkit'; import { RootState } from './store'; import { refreshTokenSuccess } from './authSlice'; import axios from 'axios'; const api = axios.create({ baseURL: '/api' }); export const refreshToken = () => { return (dispatch: Dispatch, getState: () => RootState) => { const state = getState(); const refreshToken = state.auth.refreshToken; if (!refreshToken) return Promise.reject('No refresh token available'); return api.post<{ accessToken: string }>("/auth/refreshtoken", { refreshToken }) .then(response => { dispatch(refreshTokenSuccess(response.data.accessToken)); return response.data.accessToken; }); }; };
With these changes, your Redux store will stay in perfect sync with localStorage, your HTTP interceptor will always use the latest token from Redux, and all type errors will be resolved. This setup ensures your auth data flows correctly between storage, Redux, and your API requests.
内容的提问来源于stack exchange,提问作者Geek IT

