Angular 19:为何每次注入@Injectable服务都会创建新实例?
我创建了一个标记为@Injectable的认证服务AuthService,将_isAuthenticated状态以类属性的形式保存,并通过providedIn: 'root'配置为根注入器提供。随后我将该服务注入到函数式路由守卫中,期望Angular以单例模式注入,但实际每次路由守卫生效时都会创建新的服务实例。
在服务构造函数中设置断点后发现,每次路由守卫触发都会进入构造函数,导致守卫只能获取到_isAuthenticated的初始值false,无法获取登录后的正确状态。
请问为什么会出现每次注入都创建新服务实例的问题?
相关代码
AuthService代码
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { BehaviorSubject, catchError, map, Observable, of } from 'rxjs'; import { User } from '../dto/user'; @Injectable({ providedIn: 'root' }) export class AuthService { private apiUrl = 'http://192.168.178.39:8080/auth'; private _isAuthenticated = new BehaviorSubject<boolean>(false); constructor(private http: HttpClient) { localStorage.removeItem('JWT_Token'); } login(username: string, password: string ): Observable<boolean> { return this.http.post<any>(this.apiUrl + '/login', {username, password}) .pipe( map(response => { localStorage.setItem('JWT_Token', response.token); this._isAuthenticated.next(true); return true; }), catchError(error => { console.log(error); this.logout(); return of(false); }) ); } logout(): void { localStorage.removeItem('JWT_Token'); this._isAuthenticated.next(false); } get isAuthenticated() { return this._isAuthenticated.value; } }
路由守卫代码
import { CanActivateFn } from '@angular/router'; import { AuthService } from '../service/auth.service'; import { inject } from '@angular/core'; export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); let isAuth = authService.isAuthenticated; return isAuth; };
app.routes.ts代码
import { Routes } from '@angular/router'; import { HomeComponent } from './feature/home/home.component'; import { LoginComponent } from './core/auth/login/login.component'; import { BookListComponent } from './feature/bookstore/book-list/book-list.component'; import { authGuard } from './core/auth/guard/auth.guard'; export const routes: Routes = [ {path: 'login', component : LoginComponent}, {path: '', redirectTo: 'login', pathMatch : 'full'}, {path: 'home', component : HomeComponent, canActivate : [authGuard]}, {path: 'bookstore', component : BookListComponent, canActivate : [authGuard]}, ];
app.config.ts代码
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { AuthInterceptor } from './core/auth/intercept/auth.interceptor'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withInterceptorsFromDi()), {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, ] };
问题分析与解决方案
1. 核心排查:是否真的是多实例
首先通过添加实例ID验证服务是否为单例:
@Injectable({ providedIn: 'root' }) export class AuthService { private instanceId = Math.random().toString(36).slice(2, 10); constructor(private http: HttpClient) { console.log('AuthService实例化,ID:', this.instanceId); // 原初始化逻辑 } }
- 如果控制台输出的
instanceId始终相同:说明是单例,问题出在状态维护逻辑 - 如果
instanceId每次不同:说明服务被重复提供,需排查配置
2. 常见原因及修复
原因1:服务被重复声明
检查项目中是否在组件/模块的providers数组中重复声明了AuthService(比如某个组件的@Component装饰器里添加了providers: [AuthService]),这会创建局部注入器的独立实例,覆盖根注入器的单例。
修复: 移除所有非根注入器层级的AuthService声明,仅保留@Injectable({ providedIn: 'root' })配置。
原因2:构造函数的错误逻辑干扰
你的AuthService构造函数中强制执行localStorage.removeItem('JWT_Token');,这会在服务初始化时清除token,即使是单例服务,也会导致认证状态被重置为false,让你误以为是多实例问题。
修复: 移除构造函数中的token清除逻辑,改为初始化时恢复已有token的认证状态:
constructor(private http: HttpClient) { // 初始化时读取本地存储的token,恢复认证状态 const token = localStorage.getItem('JWT_Token'); this._isAuthenticated.next(!!token); }
原因3:守卫获取状态的方式不严谨
当前守卫直接读取BehaviorSubject的value,无法实时响应状态变化。建议改为返回Observable,确保获取最新状态:
// 在AuthService中添加公共的Observable获取方法 get isAuthenticated$(): Observable<boolean> { return this._isAuthenticated.asObservable(); } // 路由守卫中使用 export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); return authService.isAuthenticated$; };
内容的提问来源于stack exchange,提问作者user29062502

