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 thetoken
which we need to refer to for the purposes of DI is declared, but not yet defined. It is also used when thetoken
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;
}
<div class="parent-container">
<h2>Parent Component</h2>
@if (!fromChild) {
<app-child/>
}
</div>
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 {}
<div class="child-container">
<h2>Child component</h2>
<app-parent [fromChild]="true"/>
</div>
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)
Hi Loukas Kotas,
Your tips are very useful
Thanks for sharing
Thank you João!