🚀 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 🙂

















SOCIAL SHARE CARD GENERATOR