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

bash
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.