I am setting up a large monorepo with multiple libraries. I am confused about how to manage providers globally. Should I define them in the root app, or use 'providedIn: root' everywhere? I want to avoid memory leaks and ensure that my singletons are actually singletons across different project boundaries.
Dependency injection in a monorepo should leverage providedIn: root for universal services while using injection tokens to enforce modular boundaries and maintain singleton integrity across project limits.
4 answers
Handling DI in a large monorepo requires a disciplined approach to scoping to avoid configuration sprawl.
- Use providedIn: root for services that are truly universal across your entire build pipeline.
- Create custom injection tokens for domain-specific logic to keep your boundaries clean.
- Avoid manual registration in the root app to keep your testing suites isolated and predictable.
Stick to providedIn: root for almost everything, as it keeps your dependency graph flat and your tree-shaking efficient. Defining providers in the root app module is an anti-pattern in a monorepo because it forces tight coupling between your infrastructure libraries and the final delivery platform.
It’s a relief to hear someone call root providers an anti-pattern, Pat Washington. I’ve been worried my current setup was creating too much coupling, and this really gives me some direction.
Pat Washington, thank you for being so direct about this. I’ve been dreading cleaning up my modules, but the tree-shaking benefits you mentioned make me feel a lot better about getting started.
I remember back in 2019 when we tried to manage global state in a monorepo by centralizing everything in the top-level app module. It became a nightmare of circular dependencies and bloated bundle sizes that took us weeks to untangle because we lost track of what injected where.
We eventually moved to a model where every library manages its own singleton providers via the providedIn token. Once we made that shift, the memory leaks vanished and the build performance improved significantly because our individual packages became truly decoupled units.
I’m still so new at this, but Jordan Dean, your experience with memory leaks sounds like the exact issues I'm facing. I'll definitely try shifting to the providedIn token approach this afternoon.
Jordan Dean, your mention of circular dependencies hits way too close to home. I’ve been struggling with those all week and the idea of untangling them for weeks actually makes me want to quit.
I've been obsessively researching these patterns, Jordan Dean, but I still feel like I'm doing it wrong. Your advice to move toward decoupled library units is exactly the validation I needed right now.
The choice between centralized registration and distributed providedIn: root centers on the trade-off between control and encapsulation. Defining providers in the root app module offers a single source of truth, which simplifies debugging for smaller teams, yet this approach frequently creates a bottleneck where the root must import every library in your graph. This configuration is fine for small projects, but it quickly falls apart in complex systems where compile-time dependency graphs should remain acyclic and granular.
Conversely, using providedIn: root everywhere is generally superior for large-scale architectures. By allowing each library to declare its own dependencies, you effectively delegate configuration to the package level, which promotes better isolation. This method is technically safer, provided you are mindful of tree-shaking, as it allows the compiler to prune unused providers automatically. While it may slightly increase the complexity of tracking singleton lifecycles, it prevents the root application from becoming a bloated configuration manifest. When you prioritize modularity, distributed DI is almost always the correct path forward.


Pat Washington, I've been nervous about changing our structure, but keeping the dependency graph flat sounds much safer. It seems like the right move to finally stop this tight coupling issue.