π Upgrading a large Angular application from v17 to v21 isnβt just a version bump β it was an architectural wake-up call.
One of the biggest shifts I had to tackle was moving the app to a fully standalone architecture. That change alone forced me to pause and ask an important question:
π βDo we actually understand how dependency injection is working in our app?β
As I dug deeper, I realized many DI patterns in the codebase worked by coincidence, not by design. They had survived for years thanks to NgModulesβ but in a standalone world, they suddenly felt fragile, confusing, or outright wrong.
π In this article, Iβll walk through real cases from a production app:
- what the original code looked like
- why it worked earlier
- where it breaks or misleads in standalone Angular
- and how to fix it cleanly and correctly
If youβre upgrading an existing Angular app or refactoring legacy DI patterns β this guide is for you.
β οΈ The migration is still ongoing β Iβll be publishing more articles as I continue the upgrade.
1. From NgModule.providers to providedIn: 'root'
β Legacy pattern (NgModule era)
This worked because:
- The module owned the DI scope
- Services were implicitly singleton within the module
β Standalone replacement
Why this is correct
-
providedIn: 'root'replaces module-level providers - Singleton behavior is preserved
- Tree-shakable and future-proof
Rule:
If a service was previously in
Module.providers, it should almost always move toprovidedIn: 'root'.
2. Services in bootstrapApplication β what really belongs there?
β What I initially had in main.ts
This works, but itβs not a correct standalone design.
β
What SHOULD be in main.ts
What bootstrapApplication is for:
β
Framework overrides (ErrorHandler, TitleStrategy, etc.)
β
Router setup & strategies
β
HttpClient + interceptors
β
App-wide configuration tokens
β
Platform / framework modules
β
Legacy services you cannot modify
What it is NOT for:
β API / CRUD services
β Business/domain services
β Feature logic
β Component state services
3. Services with Subject / BehaviorSubject: Root or not?
My use case
- App loads a master API
- Stores success/error messages in a BehaviorSubject
- Multiple components read from it
β Legacy misuse
This creates:
- Multiple instances
- Multiple subscriptions
- Hidden bugs
β Correct design
If a service stores state that many parts of the app use, it should live at the root so everyone gets the same data.
4. Services in component providers+ providedIn: 'root'
Yes, Angular allows this β but itβs dangerous.
Result
- Component gets a new instance
- App gets a different instance
β οΈ For API, auth, socket, or state services β this is almost always a bug.
5. Pipes (DatePipe& custom pipes) in providers
β What I found
β Correct usage
Or simply inject without providing:
Custom pipes used only in .ts?
π They should not be pipes β convert them to:
- utility functions
- or services
6. Components injected into other components (β οΈ critical)
β Legacy anti-pattern I found
This creates a ghost component instance:
- Not rendered
- Not part of the DOM
- Not change-detected
β
Correct approaches
Option 1: @ViewChild
Option 2: Shared service (recommended)
Components should NEVER appear in providers.
7. .spec.ts files and providers β is that okay?
Yes β tests have their own DI world.
This:
- Is test-scoped
- Does not affect runtime DI
- Is perfectly valid
No changes needed during migration.
8. GlobalErrorHandler β the exception that belongs in main.ts
Why it must stay in main.ts and not part of a component?
π§ Angular itself uses ErrorHandler, so it must be registered during app bootstrap, before any component is created.
π Error handling must be truly global β errors can occur outside component lifecycles and feature scopes.
ποΈ Components manage UI, not framework behavior, and global error handling is part of Angularβs core infrastructure.
β οΈ Providing it in components can create multiple instances, leading to inconsistent or missed error handling.
Final Takeaways
- Standalone Angular removes NgModules, not DI rules
-
providedIn: 'root'replaces most module-level providers -
bootstrapApplicationis for framework wiring only - Components must never be used as services
- Stateful services belong at root unless isolation is intentional
π If youβre migrating to standalone Angular or have run into DI-related challenges, feel free to share your questions or experiences in the comments β Iβd love to discuss them π

















Top comments (0)