Introduction
Angular's dependency injection system detects circular references and throws an error to prevent infinite recursion:
Error: Cannot instantiate cyclic dependency! AuthServiceThis 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.**Use
forwardRef** for simple direct circular dependencies: - 2.```typescript
- 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.Use the Injector directly to break the cycle at runtime:
- 2.```typescript
- 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.Extract a shared interface to decouple the services:
- 2.```typescript
- 3.// auth.interface.ts
- 4.export interface IAuthProvider {
- 5.getToken(): string;
- 6.isAuthenticated(): boolean;
- 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.Use an event-based pattern instead of direct service dependency:
- 2.```typescript
- 3.// event-bus.service.ts
- 4.@Injectable({ providedIn: 'root' })
- 5.export class EventBus {
- 6.private subject = new Subject<{ type: string; payload: any }>();
- 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 buildfrequently 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