The Problem
Angular's dependency injection system detects when two or more services depend on each other in a cycle and throws a circular dependency error. This is a design problem that prevents the application from bootstrapping.
Symptoms
- Error during application startup
- "Circular dependency detected" in console
- Application fails to bootstrap entirely
- Error names the involved services in the stack trace
Real Error Message
ERROR NullInjectorError: R3InjectorError(AppModule)[AuthService -> UserService -> AuthService]:
NullInjectorError: No provider for AuthService!
at NullInjector.get (core.js:11100)
at R3Injector.get (core.js:11267)The Circular Dependency
```typescript // auth.service.ts @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private userService: UserService) {} // AuthService -> UserService
isLoggedIn(): boolean { return !!this.userService.getCurrentUser(); } }
// user.service.ts @Injectable({ providedIn: 'root' }) export class UserService { constructor(private authService: AuthService) {} // UserService -> AuthService (CIRCULAR!)
getCurrentUser(): User | null { if (!this.authService.isLoggedIn()) return null; return this.currentUser; } } ```
How to Fix It
Fix 1: Break the Cycle with Injector
```typescript // user.service.ts import { Injector, inject } from '@angular/core';
@Injectable({ providedIn: 'root' }) export class UserService { private injector = inject(Injector);
getCurrentUser(): User | null { const authService = this.injector.get(AuthService); // Lazy resolution if (!authService.isLoggedIn()) return null; return this.currentUser; } } ```
Fix 2: Use forwardRef (for specific cases)
```typescript import { forwardRef, Inject } from '@angular/core';
@Injectable({ providedIn: 'root' }) export class UserService { constructor( @Inject(forwardRef(() => AuthService)) private authService: AuthService ) {} } ```
Note: forwardRef only works in certain scenarios. The Injector approach is more reliable.
Fix 3: Extract Shared Logic to a Third Service
```typescript // session.service.ts - the shared dependency @Injectable({ providedIn: 'root' }) export class SessionService { private currentUser: User | null = null;
setUser(user: User) { this.currentUser = user; } getUser(): User | null { return this.currentUser; } isLoggedIn(): boolean { return !!this.currentUser; } }
// auth.service.ts @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private session: SessionService) {} }
// user.service.ts @Injectable({ providedIn: 'root' }) export class UserService { constructor(private session: SessionService) {} } ```
This is the cleanest solution: both services depend on a shared third service.
Fix 4: Use Subject for Event-Based Communication
```typescript // event-bus.service.ts @Injectable({ providedIn: 'root' }) export class EventBus { private userLogin$ = new Subject<User>();
onUserLogin() { return this.userLogin$.asObservable(); } emitUserLogin(user: User) { this.userLogin$.next(user); } } ```
Services communicate through events instead of direct dependencies.