title: 'Circular dependencies · Nael Framework' description: 'Break circular references between providers and modules using forwardRef, ModuleRef, and lazy resolution patterns.'

Fundamentals

Circular dependencies

Sometimes two services need each other. Nael can resolve those cycles when you explicitly signal them with helpers like forwardRef or by lazily fetching a provider from ModuleRef. This page covers the common escape hatches and when to reach for them.

Provider-level forwardRef

Wrap the token passed to @Inject() in forwardRef() so the container evaluates the reference only after both classes are defined. This keeps constructor injection intact while avoiding undefined errors at runtime.

Mutual services
import { Inject, Injectable, forwardRef } from '@nl-framework/core';@Injectable()export class UsersService {  constructor(    @Inject(forwardRef(() => PaymentsService))    private readonly payments: PaymentsService,  ) {}  async createUser() {    await this.payments.provisionWallet();  }}@Injectable()export class PaymentsService {  constructor(private readonly users: UsersService) {}  async provisionWallet() {/* ... */}}

Consider whether the cycle reveals a tighter coupling than necessary. Sometimes extracting a shared interface or domain event eliminates the loop.

Module imports with forwardRef

Modules can also depend on each other. When both need to import the other’s exports, wrap the module reference in forwardRef inside the imports array. Nael will inline a thunk that resolves to the actual module once it is defined.

Cross-importing modules
import { Module, forwardRef } from '@nl-framework/core';@Module({  providers: [UsersService],  exports: [UsersService],  imports: [forwardRef(() => PaymentsModule)],})export class UsersModule {}@Module({  providers: [PaymentsService],  exports: [PaymentsService],  imports: [forwardRef(() => UsersModule)],})export class PaymentsModule {}

Lazy resolution via ModuleRef

ModuleRef lets you resolve providers on demand, outside of the constructor. Use this when eager injection would create a cycle but the dependency is only needed for specific code paths.

Resolving after startup
import { Injectable } from '@nl-framework/core';import { ModuleRef } from '@nl-framework/core';@Injectable()export class ReportsService {  constructor(private readonly moduleRef: ModuleRef) {}  async run() {    const exporter = await this.moduleRef.resolve(ExporterService, { strict: false });    return exporter.export();  }}

Passing {'{ strict: false }'} allows looking up tokens from imported modules. Prefer explicit module exports to keep ownership clear.

Setter or lifecycle injection

When you must reference a provider immediately after construction, you can inject a forward reference and assign it during lifecycle hooks such as onModuleInit. It’s more verbose, but it keeps constructors minimal.

Assign in onModuleInit
import { Injectable, Inject, forwardRef, OnModuleInit } from '@nl-framework/core';@Injectable()export class NotificationsService implements OnModuleInit {  private emailService!: EmailService;  constructor(@Inject(forwardRef(() => EmailService)) private readonly email: EmailService) {}  onModuleInit() {    this.emailService = this.email;  }}@Injectable()export class EmailService {  constructor(private readonly notifications: NotificationsService) {}}