Introduction

Angular's dependency injection system detects circular references and throws an error to prevent infinite recursion:

bash
Error: Cannot instantiate cyclic dependency! AuthService

This occurs when Service A depends on Service B, and Service B depends (directly or indirectly) on Service A. The DI system cannot determine which service to create first.

Symptoms

  • Application fails to bootstrap with "Cannot instantiate cyclic dependency" error
  • Error names the service involved in the cycle
  • Works in some lazy loaded modules but fails in eagerly loaded ones
  • Adding a new service suddenly triggers the error in existing services
  • Error message shows the full dependency chain (A -> B -> C -> A)

Common Causes

  • Two services that need to communicate with each other
  • Shared state service that depends on an API service which depends on an auth service
  • Event bus or notification service injected into services that also inject the event bus
  • Refactoring that introduces a new dependency creating an indirect cycle
  • Using providedIn: 'root' on services that create implicit cross-dependencies

Step-by-Step Fix

  1. 1.**Use forwardRef** for simple direct circular dependencies:
  2. 2.```typescript
  3. 3.import { Injectable, forwardRef, Inject } from '@angular/core';

@Injectable({ providedIn: 'root' }) export class AuthService { constructor(@Inject(forwardRef(() => UserService)) private userService: UserService) {} }

@Injectable({ providedIn: 'root' }) export class UserService { constructor(private authService: AuthService) {} } ```

  1. 1.Use the Injector directly to break the cycle at runtime:
  2. 2.```typescript
  3. 3.import { Injectable, Injector } from '@angular/core';

@Injectable({ providedIn: 'root' }) export class AuthService { private userService: UserService;

constructor(private injector: Injector) {}

getUser() { // Lazy resolution - avoids circular dependency at construction time if (!this.userService) { this.userService = this.injector.get(UserService); } return this.userService.getCurrentUser(); } } ```

  1. 1.Extract a shared interface to decouple the services:
  2. 2.```typescript
  3. 3.// auth.interface.ts
  4. 4.export interface IAuthProvider {
  5. 5.getToken(): string;
  6. 6.isAuthenticated(): boolean;
  7. 7.}

// auth.service.ts @Injectable({ providedIn: 'root' }) export class AuthService implements IAuthProvider { getToken(): string { return localStorage.getItem('token') || ''; } isAuthenticated(): boolean { return !!this.getToken(); } }

// user.service.ts @Injectable({ providedIn: 'root' }) export class UserService { constructor(private auth: IAuthProvider) {} // Depends on interface, not concrete class }

// In module providers { provide: IAuthProvider, useClass: AuthService } ```

  1. 1.Use an event-based pattern instead of direct service dependency:
  2. 2.```typescript
  3. 3.// event-bus.service.ts
  4. 4.@Injectable({ providedIn: 'root' })
  5. 5.export class EventBus {
  6. 6.private subject = new Subject<{ type: string; payload: any }>();
  7. 7.events$ = this.subject.asObservable();

emit(event: { type: string; payload: any }) { this.subject.next(event); } }

// Both services use the event bus instead of depending on each other ```

Prevention

  • Design services with a clear dependency hierarchy - avoid bidirectional dependencies
  • Use interfaces to abstract service contracts and break concrete class cycles
  • Prefer event-based communication (RxJS Subjects, EventEmitter) over direct service calls
  • Use Angular's inject() function (Angular 14+) for more flexible injection patterns
  • Map out service dependencies during architecture planning to identify potential cycles
  • Run ng build frequently during development to catch circular dependencies early
  • Consider using a state management solution (NgRx, Akita) for shared state instead of cross-service dependencies
  • Add a circular dependency detection tool (e.g., madge) to your CI pipeline