DEV Community

Cover image for The power of forwardRef to fix circular dependencies 🔄 in Angular
Loukas Kotas
Loukas Kotas

Posted on

The power of forwardRef to fix circular dependencies 🔄 in Angular

Introduction

Circular dependencies often pose a challenge in software development, including Angular projects. While standalone components have helped alleviate many issues, circular dependencies can still arise, presenting obstacles to efficient development.

Before delving into the solution, it's essential to acknowledge that refactoring code can often mitigate circular dependencies. However, refactoring may not always be straightforward or feasible in certain scenarios.

That's where the forwardRef comes to the rescue!

According to Angular's documentation on forwardRef:

[...] forwardRef is used when the token which we need to refer to for the purposes of DI is declared, but not yet defined. It is also used when the token which we use when creating a query is not yet defined.

forwardRef is also used to break circularities in standalone components imports.

For more detailed information, I strongly recommend to have a look at angular documentation and the angular codebase.

Under the Hood: The Mechanism of forwardRef

The concept of forwardRef is closely related to JavaScript closures. Both rely on Deferred Resolutions of values (such as services or components) until a later point in execution. While closures use captured variables as placeholders, forwardRef employs tokens for similar purposes.

Circular dependencies between components

Consider resolving circular dependencies between standalone components – a common scenario in Angular development. Suppose we have a Parent component injecting a Child component and vice versa.

@Component(
  selector: 'app-parent',
  templateUrl: 'parent.component.html',
  standalone: true,
  imports: [ChildComponent],
)
class ParentComponent {
  @Input() fromChild!: boolean;
}
Enter fullscreen mode Exit fullscreen mode
<div class="parent-container">
  <h2>Parent Component</h2>
  @if (!fromChild) {
    <app-child/>
  }
</div>
Enter fullscreen mode Exit fullscreen mode

To prevent infinite loop rendering, the Parent component includes an @Input called fromChild, indicating whether it's called from a child component. If so, it avoids re-rendering the child component to prevent further recursion.

@Component({
  selector: 'app-child',
  templateUrl: 'child.component.html',
  standalone: true,
  imports: [
    //👇 Here is where the magic happens
    forwardRef(() => ParentComponent)], 
})
export class ChildComponent {}
Enter fullscreen mode Exit fullscreen mode
<div class="child-container">
  <h2>Child component</h2>
   <app-parent [fromChild]="true"/>
</div>
Enter fullscreen mode Exit fullscreen mode

Without utilizing the forwardRef mechanism, an undefined error would occur:

ERROR TypeError: Cannot read properties of undefined

Using forwardRef ensures the reference of the component instantiation is fetched during runtime, resolving any undefined errors that would otherwise occur without it.

I hope this explanation provides clarity on the usage of forwardRef, but it's crucial to exercise caution to avoid overuse or misuse, which can potentially deteriorate the codebase.

Explore a live demonstration on StackBlitz for a hands-on experience: Working Example

Thank you for exploring this topic with me. Feel free to connect with me on LinkedIn or GitHub and share your thoughts.

Happy Coding! 🍻

Top comments (2)

Collapse
 
jangelodev profile image
João Angelo

Hi Loukas Kotas,
Your tips are very useful
Thanks for sharing

Collapse
 
loukaskotas profile image
Loukas Kotas

Thank you João!