Components = UI units: template + class + styles. Use OnPush change detection with immutable @Input to cut renders 80%.
@ViewChild is undefined until ngAfterViewInit. Accessing it in ngOnInit is the #1 timing bug.
Services use hierarchical DI. providedIn:'root' creates a singleton in the root injector. If the same service is ALSO listed in a lazy module's providers array, Angular creates TWO instances — state updates in one won't be seen in the other.
HTTP interceptors are middleware. Order matters: auth (adds token), loading (spinner), error (handles 401). Mismatch order breaks functionality.
Route guards: canMatch prevents lazy module from loading at all. canActivate runs after module loads. Use canMatch for role-based access to admin modules.
Production killer: RouterModule.forRoot() in a lazy-loaded feature module creates a second Router instance. Navigation events fire twice, views don't update, no error thrown — silent failure for hours.
✦ Definition~90s read
What is Introduction to Angular?
Angular is a TypeScript-based framework built by Google for constructing client-side applications that don't fall apart when you add a second developer. It's not a library you glue together with duct tape — it's a full platform with its own router, HTTP client, forms module, and dependency injection system.
★
Think of an Angular app like a large restaurant chain.
What sets Angular apart from React or Vue is the architecture. Angular enforces a structure from day one: components, services, modules, and a clear separation of concerns. That sounds boring until you inherit a codebase where every component talks directly to the database. Angular's guardrails prevent that nonsense.
Angular handles change detection for you. It uses zones to track asynchronous operations and update the DOM only when necessary. You don't write useEffect or setState — you declare bindings in the template, and the framework does the rest. For enterprise apps with complex state, this means less boilerplate and fewer bugs.
The trade-off? Angular has a steeper learning curve. But you get a complete toolkit for routing, forms, HTTP, testing, and animations out of the box. No chasing npm packages that break on every major release.
Plain-English First
Think of an Angular app like a large restaurant chain. Each branch (component) handles its own dining room and customer interactions independently. The franchise rulebook (module) groups related branches together and defines what equipment they share. The central kitchen supplier (service) provides ingredients to every branch without each one needing its own farm. The security guard at the door (route guard) checks if you're allowed in. The kitchen manager (interceptor) adds seasoning to every dish before it leaves the kitchen, without the chef knowing. When one branch runs out of a sauce, it calls the supplier — it doesn't try to grow tomatoes itself. That's Angular's architecture in one lunch conversation.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
A team I consulted for spent three weeks hunting a memory leak that was crashing their Angular dashboard every four hours in production. Root cause: they were instantiating a new HttpClient inside a component instead of injecting a shared service, spinning up a fresh connection pool on every component mount and never releasing it. Four hours was exactly how long it took to exhaust the browser's connection limit. The fix was twelve characters. The three weeks were pure ignorance of how Angular's dependency injection actually works.
Angular's architecture — components, modules, services, guards, interceptors — isn't ceremony for ceremony's sake. It's a hard answer to a real problem: how do you build a 200-screen enterprise app with a team of 15 developers without it collapsing into a dependency nightmare? Without this structure you get components doing HTTP calls, state management, DOM manipulation, and business logic all in one file. I've seen it. It looks like someone let an intern rewrite jQuery in TypeScript. The separation isn't optional architecture philosophy — it's load-bearing.
After working through this, you'll be able to wire up a component that consumes a singleton service, register it correctly in a feature module or standalone component, know exactly why providedIn: 'root' exists and when NOT to use it, implement HTTP interceptors for auth and error handling, protect routes with guards, and spot the most common architectural mistakes before they hit your code review. You'll also understand why lazy-loaded modules break service singletons if you don't know what you're doing — which is the interview question that separates people who've actually shipped Angular from people who've done the Tour of Heroes.
Why Angular DI Duplicates Lazy Module Singletons
Angular's dependency injection (DI) creates a separate injector for each lazy-loaded module. When a service is provided in a lazy module, that module's injector instantiates its own copy of the service — even if the same service was already provided in the root injector. This breaks the singleton contract: two different instances of the same service class exist, one in the root and one in the lazy module. The core mechanic is that Angular's injector hierarchy is tree-shaped, and each lazy module gets its own child injector. Services provided in the module metadata are registered in that child injector, not the root. This means any component or service inside that lazy module receives the module-local instance, while the rest of the app uses the root instance. In practice, this leads to state inconsistency, duplicated HTTP requests, and memory leaks. For example, a shared auth service holding a user token will have two separate tokens — the app might log out on one route but stay logged in on another. The rule of thumb: always provide singleton services in the root injector using providedIn: 'root'. Only provide in a lazy module if you explicitly need a new instance scoped to that module (e.g., a per-module cache).
⚠ Singleton Illusion
A service provided in a lazy module's providers array is NOT a singleton — it's a new instance per module load, even if the same class is provided elsewhere.
📊 Production Insight
A team provided a shared UserPreferencesService in both AppModule and a lazy AdminModule. Users reported that changes made in admin pages (e.g., theme toggle) were lost when navigating to non-admin pages.
Symptom: The admin module had its own UserPreferencesService instance, so preferences saved there never reached the root instance used by the rest of the app.
Rule of thumb: Never provide a stateful service in a lazy module's providers array — always use providedIn: 'root' unless you explicitly need a module-scoped instance.
🎯 Key Takeaway
Lazy modules create child injectors, so services provided there are NOT root singletons.
Always use providedIn: 'root' for truly global services to avoid duplication.
If you need a module-scoped instance, be explicit and document why — it's an intentional design choice, not a default.
thecodeforge.io
Introduction To Angular
Components: The Unit of UI — and Why They Must Stay Dumb
A component's only job is to display data and capture user intent. That's it. The moment a component starts making HTTP calls directly, manipulating global state, or containing business logic, you've built a monolith inside a framework that was designed to stop you from doing exactly that.
Before Angular, teams building large SPAs with frameworks like Backbone or early AngularJS (v1) routinely ended up with controllers that were thousands of lines long. Testing was impossible without spinning up the entire app. Reuse was a joke. A component-based architecture forces a hard boundary: the component owns the template and the interaction logic wired to that template, nothing else.
Every Angular component is defined by three things: a TypeScript class (the brain), a template (the face), and styles (the skin). The @Component decorator is what tells Angular's compiler to treat this class as a UI unit. The selector is how you stamp it into other templates. Change detection is how Angular knows when to re-render — and this is where most intermediate developers are still fuzzy. Angular's default change detection strategy (CheckAlways) rerenders the component on every event cycle. Switch to OnPush for any component receiving data via @Input and you cut unnecessary renders dramatically. On a dashboard with 80 components, this is the difference between 60fps and a janky mess.
Lifecycle hooks matter more than most tutorials admit. ngOnInit fires once after the first ngOnChanges — use it for initialization. ngOnChanges fires every time an @Input reference changes — use it to react to parent data updates, but keep it fast. ngAfterViewInit fires after the component's view and all child views are initialized — this is where you can safely access @ViewChild references. ngOnDestroy fires before the component is destroyed — this is your last chance to clean up subscriptions, timers, and event listeners. I've seen teams skip ngOnDestroy entirely and wonder why their app leaks memory. The hook exists for a reason. Use it.
If you mutate a property inside an @Input object (e.g., order.status = 'CONFIRMED') instead of passing a new object reference, OnPush will never detect the change and your UI will silently show stale data. Always treat @Input objects as immutable. Return a new object from the parent: { ...order, status: 'CONFIRMED' }. This is the single most common OnPush bug I've seen in code reviews.
📊 Production Insight
OnPush + immutable @Input is the performance win, but teams accidentally mutate objects.
The symptom: a button click updates the server but the UI doesn't change. No error. No warning.
The fix: change detection only runs when the reference changes, not when a property mutates.
Rule: Never mutate @Input objects. Always create a new reference.
🎯 Key Takeaway
Component owns UI only — not data fetching, not business logic, not global state.
OnPush with immutable @Input cuts re-renders by 80% on dashboards.
ngOnDestroy is where subscriptions die — use it, or leak memory.
Choose Change Detection Strategy
IfComponent only receives data via @Input and emits events via @Output
→
UseUse OnPush. Best performance. Requires immutable @Input.
IfComponent has internal timer, animation, or uses ChangeDetectorRef manually
→
UseOnPush works with markForCheck(). Use it deliberately.
IfComponent is simple and has no performance constraints
→
UseDefault (CheckAlways) is acceptable but not optimal for lists or dashboards.
IfComponent is deeply nested with many children relying on global service state
→
UseOnPush + async pipe. The async pipe triggers change detection on emission.
Services and Dependency Injection: Where Your Architecture Actually Lives
Services are where your business logic lives. Not in components, not in utility files, not in a god-object store — in injectable services with a single, clear responsibility. The reason Angular's DI system exists is that it solves a problem that's invisible when your app is small and catastrophic when it isn't: how do you ensure that the same instance of a stateful object is shared across dozens of components without those components knowing about each other?
providedIn: 'root' is the correct default for most services. It registers the service with the root injector, making it a true singleton for the app's lifetime. Angular's tree-shaking will also remove it from the bundle if nothing actually injects it — something the old providers: [] module pattern doesn't give you. The one time you don't want providedIn: 'root' is when you need component-level or lazy-module-level isolation: a form state service that should reset when a component is destroyed, or a polling service that should stop when a feature module is unloaded.
The DI system is hierarchical. Root injector at the top, module injectors in the middle, component injectors at the bottom. When a component asks for a dependency, Angular walks up the injector tree until it finds a provider. If you provide a service at the component level (via the component's providers array), each instance of that component gets its own service instance. I've used this deliberately for a shopping cart draft service that needed to be isolated per product modal — each modal got its own state, destroyed with the modal.
The inject() function (available since Angular 14) is the modern alternative to constructor injection. It's more composable — you can call it inside functions, computed properties, and conditional blocks. I've fully switched to inject() in standalone components. Constructor injection still works, but inject() is cleaner and plays better with TypeScript's strict mode.
⚠ Service Provided in Both Root and Lazy Module = Two Instances
If a service has providedIn: 'root' AND is listed in a lazy-loaded module's providers array, Angular creates two instances: one in the root injector, one in the lazy module's injector. Components in that module get the lazy instance; everything else gets the root instance. State changes in one instance are invisible to the other. Symptom: service state updates on one page but not another, seemingly at random. Fix: pick one. Use providedIn: 'root' and remove it from every module's providers array, unless you explicitly want isolated instances.
📊 Production Insight
The singleton service pattern is the most common DI mistake in Angular.
Stateful services (auth, user prefs, polling) must be singletons.
If you provide them in a lazy module, the state is isolated to that module's subtree.
Rule: providedIn:'root' is for app-wide singletons. Lazy-loaded modules must NOT re-provide them.
Don't list root-provided services in lazy module providers — that creates a second instance.
Inject() is cleaner than constructor injection. Use it in standalone components.
thecodeforge.io
Introduction To Angular
What Is Angular, Really? The Framework That Eats Complexity for Breakfast
Angular is a TypeScript-based framework built by Google for constructing client-side applications that don't fall apart when you add a second developer. It's not a library you glue together with duct tape — it's a full platform with its own router, HTTP client, forms module, and dependency injection system.
What sets Angular apart from React or Vue is the architecture. Angular enforces a structure from day one: components, services, modules, and a clear separation of concerns. That sounds boring until you inherit a codebase where every component talks directly to the database. Angular's guardrails prevent that nonsense.
Angular handles change detection for you. It uses zones to track asynchronous operations and update the DOM only when necessary. You don't write useEffect or setState — you declare bindings in the template, and the framework does the rest. For enterprise apps with complex state, this means less boilerplate and fewer bugs.
The trade-off? Angular has a steeper learning curve. But you get a complete toolkit for routing, forms, HTTP, testing, and animations out of the box. No chasing npm packages that break on every major release.
Angular's change detection uses Zone.js to auto-detect async events. If you see performance issues, check if you've got too many bindings on a single component — extract child components with OnPush strategy.
🎯 Key Takeaway
Angular is a full-featured framework, not a library. It gives you structure and tooling for large apps, but demands you play by its rules from the start.
Angular Versions: Why You Should Care About the Migration Tax
Angular has gone through major changes since its 2.0 release in 2016. The AngularJS (1.x) to Angular 2+ migration was a complete rewrite — different architecture, different language, different templating. Teams that stayed on AngularJS got stuck with security holes and library rot.
Today, Angular follows semantic versioning with a predictable release cadence. Major versions come every six months. The good news: since Angular 2, the API has been stable. Upgrading from version 15 to 18 is straightforward — usually an afternoon's work with ng update.
But here's the trap: Angular's modules (NgModules) were deprecated in favor of standalone components starting in version 14. If you're starting a new project today, don't generate modules. Use --standalone flag on ng generate. Otherwise you're writing boilerplate that the framework doesn't need.
Version 17 introduced signals — a reactive primitive that replaces zones for change detection in many cases. Version 18 added @for loops and @if blocks to templates. The framework is moving toward a more functional, less magic-driven approach. Learn signals now, because zones are on the chopping block.
Version 19 introduced the inject function over constructor injection for services. More concise, less boilerplate. Adopt it.
Don't mix standalone components with NgModules in the same project unless you have a migration plan. It works, but it creates confusion about import conventions and adds complexity to your module tree.
🎯 Key Takeaway
Always start a new Angular project with standalone components. Learn signals. Update every 6 months to avoid a painful jump across multiple versions.
thecodeforge.io
Introduction To Angular
Prerequisites: What You Actually Need Before Angular Makes Sense
Before you touch Angular, you need to be comfortable with TypeScript. Not just "I saw a type annotation once" — you need to understand generics, decorators, interfaces, and the type system. Angular is TypeScript-first. If you fight the type system, Angular will fight you back.
Second: you need Node.js 18 or later and npm. Angular CLI is an npm package. Run npm install -g @angular/cli to install it globally. Then ng new my-app scaffolds a project. If you've never used a terminal, fix that first.
Third: understand the difference between a component, a service, and a module. Components display data. Services contain business logic and data fetching. Modules (if you must use them) group components and services together. Get this wrong, and you'll end up with components making HTTP calls directly — a security and testability nightmare.
Fourth: learn RxJS observables. Angular uses them for HTTP, router events, and form value changes. You don't need to be an RxJS guru, but you must understand .subscribe(), pipe(), and map(). Without it, your async code will leak memory.
Finally: you need a code editor with TypeScript support. VS Code with the Angular Language Service extension gives you template type-checking and autocomplete. Don't code Angular in Notepad.
InstallAndScaffold.shJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — javascript tutorial
# CheckNode version
node --version
# Should output v18.x or higher
# InstallAngularCLI globally
npm install -g @angular/cli
# Scaffoldnew project with standalone components
ng new enterprise-app --standalone --style=scss
# Serve the app
cd enterprise-app
ng serve
Output
✔ Packages installed successfully.
✔ Project created at /home/dev/enterprise-app
** Angular Live Development Server is listening on localhost:4200 **
Use --standalone flag when creating a new project to skip NgModule generation. This reduces your initial file count by 30% and aligns with Angular's future direction.
🎯 Key Takeaway
Master TypeScript, RxJS basics, and Node before Angular. Install Angular CLI globally. Use standalone components from day one.
● Production incidentPOST-MORTEMseverity: high
The Lazy Module That Broke the Singleton
Symptom
User logs out normally. Main app redirects to login page. But the admin dashboard (lazy-loaded) remains accessible. Authentication checks in the admin module pass. Admin data is still visible. Reloading the page fixes it because the lazy module re-initialises.
Assumption
The team assumed providedIn: 'root' guaranteed a single instance everywhere. They didn't know that a lazy-loaded module with its own injector would override the root provider for that module's subtree.
Root cause
AuthService had @Injectable({ providedIn: 'root' }). The lazy-loaded AdminModule also had providers: [AuthService] because the developer saw an error about missing provider and added it. Angular's DI hierarchy: root injector created one instance. The lazy module's injector created a second instance for components inside that module. The main app used the root instance. The admin dashboard used the lazy instance.
When the user logged out, the main app called clearSession() on the root instance. The lazy instance's state remained unchanged. The admin dashboard's guards checked the lazy instance and saw an active session.
The team never noticed because the app worked fine most of the time — until a logout scenario exposed the divergence.
Fix
1. Removed AuthService from AdminModule's providers array entirely. Only providedIn: 'root' remained.
2. For any service that must be isolated per module (e.g., per-module form state that should reset on module unload), used component-level providers or a factory provider with useFactory.
3. Added an Angular rule in CI: ng lint with custom ESLint rule preventing providers arrays in lazy modules for root-provided services.
4. Documented the rule: 'A service with providedIn:'root' must NEVER be listed in the providers array of any lazy-loaded module.'
Key lesson
providedIn:'root' + providers array in lazy module = TWO instances. The lazy instance shadows the root instance for that module's subtree.
A service that is stateful (auth, user preferences, polling service) cannot be provided in both root and lazy module — the state will diverge.
If you need a service that resets on module unload, provide it at the component level, not in the lazy module's providers array.
Write a custom ESLint rule to detect services annotated with providedIn:'root' that appear in any NgModule.providers or Component.providers array.
Production debug guideSymptom → Action mapping for common Angular failures in production5 entries
Symptom · 01
User logs out but still sees protected data in lazy-loaded module
→
Fix
Check if auth service is provided in both providedIn:'root' AND in the lazy module's providers. That creates two instances. Remove from lazy module providers.
Symptom · 02
Navigation events fire twice, URL updates but view doesn't change
→
Fix
Check for RouterModule.forRoot() in a lazy-loaded feature module. That creates a second Router instance. Replace with forChild().
Symptom · 03
@ViewChild element is undefined in component code
→
Fix
@ViewChild is not available until ngAfterViewInit. Move access to ngAfterViewInit. Also check if *ngIf is hiding the element — use { static: false }.
Symptom · 04
HttpInterceptor not adding auth headers to some requests
→
Fix
Check interceptor order in providers array. Auth interceptor must come BEFORE other interceptors that might modify the request. Also verify request is going through Angular HttpClient, not native fetch.
Symptom · 05
Component renders stale data even though service state changed
→
Fix
Check if component uses OnPush change detection and @Input object was mutated (same reference). Use immutable updates: pass a new object reference. Also check if async pipe is used correctly.
★ Angular Debug Cheat SheetFast diagnostics for common Angular production issues.
Checked if service has two instances (auth state divergence)−
What Is Angular, Really? The Framework That Eats Complexity
StandaloneComponent.ts
@Component({
Angular Versions
InstallAndScaffold.sh
node --version
Prerequisites
Key takeaways
1
A component that calls HttpClient directly is a design failure, not a shortcut. The component owns the UI; a service owns the data fetching. Mix them and you can't unit test, can't reuse, and can't reason about state.
2
RouterModule.forRoot() in a feature module creates a second Router instance and breaks navigation in ways that don't throw errors
they just cause unpredictable behaviour under load. Always forChild() in feature modules.
3
Reach for providedIn
'root' by default for any stateless or globally-shared service. Switch to component-level providers only when you need isolated, component-scoped state that must be destroyed with its host — like a draft form state or a per-dialog polling stream.
4
The async pipe isn't just a convenience
it's architectural correctness. It guarantees unsubscription on component destroy, triggers OnPush change detection correctly, and removes an entire class of memory leaks.
5
Standalone components are the default in Angular 17+. Use them for all new code. NgModules still make sense for library packaging and large legacy migrations, but standalone removes the 'where do I put this component?' debate entirely.
6
HTTP interceptors are middleware for your API layer. Use them for auth token injection, global error handling, loading state, and request logging. Never put auth logic or error handling inside individual services or components.
7
Route guards centralise access control. Use functional guards (CanActivateFn) for simple checks. Use canMatch over canActivate when you want to prevent a lazy module from loading entirely for unauthorised users.
8
takeUntilDestroyed replaces the destroy$ Subject pattern with zero boilerplate. Combined with toSignal() for Observable-to-signal conversion, modern Angular cleanup is structurally leak-proof.
9
If a service is provided in both root and a lazy module, you get two instances. State updates in one are invisible in the other. This is the single most common DI bug in large Angular apps.
10
OnPush with immutable @Input is the performance win. Mutate objects and the UI doesn't update. No error. Just stale data. Always pass new references.
Common mistakes to avoid
9 patterns
×
Providing a root service in a lazy module's providers array — duplicate instance
Symptom
State updates made in one part of the app are invisible to components in the lazy module. Logout doesn't clear session in lazy module. Two separate service instances exist. Adding console.log with random ID shows different values in different parts of the app.
Fix
Remove the service from the lazy module's providers array entirely. Only providedIn: 'root' should remain. For services that must be isolated per module (e.g., form state that should reset when the module unloads), provide them at the component level, not the module level.
×
Calling RouterModule.forRoot() in a lazy-loaded feature module instead of forChild()
Symptom
Navigation events fire twice (NavigationStart, NavigationEnd appear twice in logs). URL updates in browser address bar but the view doesn't change. No error thrown — just silent failure.
Fix
Replace RouterModule.forRoot(routes) with RouterModule.forChild(routes) in every module except the root AppModule. Only the root module should use forRoot().
×
Accessing @ViewChild in ngOnInit — returns undefined
Symptom
Component property that should reference a DOM element or child component is undefined. No error is thrown — the property just never gets set. The template uses the element, but the component code can't interact with it.
Fix
Move @ViewChild access to ngAfterViewInit. The view (including child components) is guaranteed to be fully initialized there. If the element is conditionally shown (e.g., with *ngIf), use { static: false } and access it after the condition becomes true.
×
Mutating @Input objects with OnPush change detection
Symptom
UI shows stale data even though the underlying data in the service or parent has updated. The server confirms the change, but the screen doesn't reflect it. No errors in console.
Fix
Treat @Input objects as immutable. Instead of this.order.status = 'CONFIRMED', pass a new object: this.order = { ...this.order, status: 'CONFIRMED' }. This creates a new reference, which triggers OnPush change detection.
×
Subscribing to Observables in components without cleanup
Symptom
Memory usage grows over time. After navigating away from a page and back, the component mounts again but the old subscriptions are still active. Network requests continue in the background even after the component is destroyed.
Fix
Use the async pipe in templates whenever possible. For imperative subscriptions, use takeUntilDestroyed() (Angular 16+) or a destroy$ Subject with takeUntil in ngOnDestroy.
×
Incorrect interceptor order — error interceptor before auth interceptor
Symptom
Auth tokens are missing from requests sent after error handling. The error interceptor catches a 401 but doesn't retry with a refreshed token because the auth interceptor hasn't run yet in the retry path.
Fix
Register interceptors in this order: AuthInterceptor (adds token), LoadingInterceptor (spinner), ErrorInterceptor (handles errors). Order is determined by registration order in the providers array.
×
Importing BrowserModule in a feature module
Symptom
Error: 'BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.' Build fails.
Fix
BrowserModule is only for the root AppModule. Use CommonModule in every feature and shared module. CommonModule provides NgIf, NgFor, and other core directives.
×
Using *ngFor on large lists without trackBy
Symptom
DOM nodes unmount and remount on every data refresh. Scrolling position resets, CSS animations restart, input focus is lost inside list items. Perf degrades on large lists.
Fix
Always add trackBy to ngFor on data coming from observables or HTTP responses: ngFor="let item of items; trackBy: trackById". Provide a function that returns a stable, unique identifier.
×
Hardcoding API URLs in services instead of using environment files
Symptom
The app works in development but hits localhost:3000 in staging/production. Deploying to a new environment requires code changes and a redeploy.
Fix
Store API base URLs in environment.ts and environment.prod.ts. Use angular.json file replacements to swap them at build time. Reference environment.apiBase in services.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
Angular's dependency injector is hierarchical. If a service is provided ...
Q02SENIOR
When would you choose component-level service providers over providedIn:...
Q03SENIOR
What happens to an active RxJS subscription inside an Angular service th...
Q04SENIOR
What is the difference between canActivate and canMatch route guards, an...
Q05SENIOR
Explain the difference between @ViewChild and @ContentChild. When is eac...
Q06SENIOR
How do HTTP interceptors work in Angular, and why does registration orde...
Q07SENIOR
What is the difference between standalone components and NgModule-based ...
Q08SENIOR
How do Angular Signals differ from RxJS Observables, and when would you ...
Q01 of 08SENIOR
Angular's dependency injector is hierarchical. If a service is provided at both the root injector and inside a lazy-loaded module's providers array, how many instances get created, and how does Angular decide which instance to inject into a component inside that lazy module?
ANSWER
Angular creates two separate instances. The root injector creates one instance at app startup. The lazy-loaded module's injector creates another instance when the module is loaded (on first navigation to a route that triggers lazy loading). Resolution follows the injector tree: Angular walks up from the component's injector to its parent injectors until it finds a provider. For a component inside the lazy-loaded module, the lazy module's injector is closer in the tree than the root injector, so Angular uses the lazy module's instance. For a component outside the lazy module (e.g., in the main app), Angular uses the root instance. This is why state updates in one instance are invisible in the other — they're different objects entirely. The fix: never list a providedIn: 'root' service in a lazy module's providers array.
Q02 of 08SENIOR
When would you choose component-level service providers over providedIn: 'root' in a production application — and what's the specific failure mode if you choose root when you should have chosen component-level?
ANSWER
Use component-level providers when each instance of a component needs its own isolated service instance. Example: a shopping cart modal component that has its own draft state. Each modal should start with an empty cart, independent of other modals. With providedIn: 'root', if you open two modals, they'd share the same cart — items added in one would appear in the other. Failure mode: state leakage between components. Another example: a form wizard component where each wizard instance needs its own form state. With root, the state of one wizard would overwrite another. The rule: use component-level providers for state that should be destroyed with the component; use root for state that should survive component destruction and be shared globally.
Q03 of 08SENIOR
What happens to an active RxJS subscription inside an Angular service that uses providedIn: 'root' when the component that triggered the subscription is destroyed — and what's the production consequence of not handling this correctly?
ANSWER
The subscription continues to live because the service is a singleton that lives for the entire app lifetime. If the subscription is not cleaned up, it will keep receiving values and executing callbacks even after the component that triggered it is destroyed. This causes: (1) memory leaks — the subscription and its captured variables (including the destroyed component reference) prevent garbage collection, (2) phantom operations — the callback may still try to update the destroyed component's state, causing NPEs or 'view destroyed' errors, (3) continued network requests — if the subscription triggers HTTP calls, they'll run indefinitely. The production consequence: the app gradually consumes more memory with each navigation, eventually leading to browser tab crashes (often after 4-8 hours of usage). The fix: in the service, provide an ngOnDestroy method that cleans up any app-wide subscriptions when the service itself is destroyed (on app shutdown). For subscriptions tied to a component's lifecycle, use takeUntilDestroyed() in the component, not in the service.
Q04 of 08SENIOR
What is the difference between canActivate and canMatch route guards, and when would you use canMatch instead of canActivate for a lazy-loaded admin module?
ANSWER
canActivate runs AFTER the route matches but BEFORE activation. The lazy module's code may already be downloaded when canActivate executes. canMatch runs BEFORE route matching — if it returns false, the route is skipped entirely, and the lazy module is never downloaded. Use canMatch for role-based access control on lazy-loaded modules because it prevents unauthorized users from downloading the module's code at all. Use canActivate for auth checks that need to run after the route parameters are resolved, or for checking that the user hasn't already been authenticated before redirecting. Example: an admin module with sensitive code that should not be downloaded by regular users — use canMatch. A profile edit route that needs to check if the logged-in user matches the profile ID in the URL — use canActivate.
Q05 of 08SENIOR
Explain the difference between @ViewChild and @ContentChild. When is each one undefined, and in which lifecycle hook do they become available?
ANSWER
@ViewChild queries for elements or components that are declared in the component's own template (the component's view). @ContentChild queries for elements or components that are projected into the component via <ng-content> from a parent component.
@ViewChild is available in ngAfterViewInit. Before that (e.g., in ngOnInit), it returns undefined because the view hasn't been fully initialised yet.
@ContentChild is available in ngAfterContentInit. Before that, it also returns undefined. The content (projected elements) is prepared earlier than the view, but still after ngOnInit.
Common mistake: accessing @ViewChild in ngOnInit and getting undefined. Fix: move the logic to ngAfterViewInit. If the element is conditionally shown with *ngIf, use { static: false } and access it after the condition becomes true (still in ngAfterViewInit).
Q06 of 08SENIOR
How do HTTP interceptors work in Angular, and why does registration order matter? What happens if you register an error interceptor before an auth interceptor?
ANSWER
Interceptors are middleware for HTTP requests. They implement the HttpInterceptor interface with an intercept() method that receives the outgoing request and a next function to pass the request to the next interceptor. The chain executes in registration order.
Registration order matters because each interceptor can modify the request before passing it on. Typical order: (1) Auth interceptor — adds token header, (2) Logging interceptor — logs request/response, (3) Error interceptor — catches errors and handles 401.
If you register error interceptor before auth interceptor, the token may not be on the request when the error interceptor retries after a 401. The retry will also miss the token. The result: refresh token flow breaks, and users get stuck with 401 errors instead of transparently re-authenticating.
Another example: a loading interceptor that counts active requests. If it's registered after an interceptor that modifies the request (like auth), the loading count will be accurate for the final request, but the loading spinner might hide before the modified request is actually sent if ordering is wrong.
Q07 of 08SENIOR
What is the difference between standalone components and NgModule-based components? When would you still choose NgModules in a new Angular 17+ project?
ANSWER
Standalone components declare their own dependencies directly in their imports array. They don't need an NgModule wrapper. They can be lazy-loaded individually via loadComponent(). They are the default in Angular 17+.
NgModule-based components must be declared in exactly one module's declarations array, and the module must import their dependencies. Lazy loading requires a module wrapper via loadChildren().
Use standalone for all new components in new projects. Use NgModules only when: (1) you're packaging a library that needs to work with both standalone and NgModule-based consumers, (2) you have a large legacy codebase where migration cost exceeds benefit, or (3) you need to group many components under a single lazy-loaded route with shared providers and want to avoid repeating imports in every component.
In practice, 90% of new Angular code should be standalone. NgModules add boilerplate without benefits for most application code.
Q08 of 08SENIOR
How do Angular Signals differ from RxJS Observables, and when would you use toSignal() vs subscribing to an Observable directly?
ANSWER
Signals are synchronous values with automatic dependency tracking. They don't require subscription management. Observables are asynchronous streams that require subscription and unsubscription.
Use signals for: local component state (selected tab, form values, UI flags), computed/derived values that depend on other signals, and template bindings where you want synchronous access to the current value.
Use Observables for: HTTP responses, WebSocket streams, timer-based intervals, event buses, and any asynchronous data flow that requires operators like debounceTime, switchMap, or retry.
Use toSignal() to convert an Observable to a signal. This is useful for bridging async services (which typically return Observables) into a signal-based component state. It automatically subscribes and unsubscribes when the component is destroyed. Use it when you want to handle Observable data reactively with signal semantics (e.g., in a template with @if instead of async pipe).
Subscribe to Observables directly only when you need to run side effects in the callback that can't be expressed in a template. In those cases, always use takeUntilDestroyed() to prevent leaks.
01
Angular's dependency injector is hierarchical. If a service is provided at both the root injector and inside a lazy-loaded module's providers array, how many instances get created, and how does Angular decide which instance to inject into a component inside that lazy module?
SENIOR
02
When would you choose component-level service providers over providedIn: 'root' in a production application — and what's the specific failure mode if you choose root when you should have chosen component-level?
SENIOR
03
What happens to an active RxJS subscription inside an Angular service that uses providedIn: 'root' when the component that triggered the subscription is destroyed — and what's the production consequence of not handling this correctly?
SENIOR
04
What is the difference between canActivate and canMatch route guards, and when would you use canMatch instead of canActivate for a lazy-loaded admin module?
SENIOR
05
Explain the difference between @ViewChild and @ContentChild. When is each one undefined, and in which lifecycle hook do they become available?
SENIOR
06
How do HTTP interceptors work in Angular, and why does registration order matter? What happens if you register an error interceptor before an auth interceptor?
SENIOR
07
What is the difference between standalone components and NgModule-based components? When would you still choose NgModules in a new Angular 17+ project?
SENIOR
08
How do Angular Signals differ from RxJS Observables, and when would you use toSignal() vs subscribing to an Observable directly?
SENIOR
FAQ · 8 QUESTIONS
Frequently Asked Questions
01
What is the difference between providedIn: 'root' and adding a service to a module's providers array?
providedIn: 'root' registers the service with the root injector as a singleton, is tree-shakeable (removed from the bundle if never injected), and works correctly across lazy-loaded modules. Adding a service to a module's providers array creates a new instance scoped to that module's injector, which breaks the singleton guarantee and can cause two separate instances to exist if the module is lazy-loaded. The rule of thumb: use providedIn: 'root' for everything shared app-wide; use providers: [] at the component level only when you explicitly need per-component instance isolation.
Was this helpful?
02
What is the difference between an Angular module's declarations and exports arrays?
declarations lists components, directives, and pipes that belong to this module's compiler — they can be used inside this module's templates. exports makes a subset of those declarations (or imported modules) available to any module that imports this one. A component in declarations but not exports is private to the module. The practical rule: only export what other modules actually need to consume. Exporting everything inflates every consumer's compilation context and adds to bundle analysis noise.
Was this helpful?
03
How do I stop an Angular service from leaking memory when it uses an RxJS interval or timer?
Use a Subject as a destroy signal and pipe takeUntil(this.destroy$) onto every observable inside the service. In ngOnDestroy, call this.destroy$.next() and this.destroy$.complete(). For root-provided services, ngOnDestroy fires on app teardown. For component-scoped services (provided in the component's providers array), ngOnDestroy fires when the host component is destroyed — which is why component-scoped providers are the correct choice for services tied to a component's lifecycle.
Was this helpful?
04
Why does OnPush change detection sometimes show stale data even after state has clearly changed?
OnPush only re-renders when an @Input reference changes, an event originates inside the component, an async pipe emits, or change detection is manually triggered via ChangeDetectorRef.markForCheck(). If you mutate an object that's passed as @Input — e.g., order.status = 'CONFIRMED' — the reference hasn't changed, so OnPush skips the render. The fix is always immutable updates: pass a new object ({ ...order, status: 'CONFIRMED' }) so the reference comparison returns false and Angular schedules a re-render.
Was this helpful?
05
What is the difference between standalone components and NgModule-based components?
Standalone components declare their own dependencies in their imports array — no NgModule wrapper needed. They can be lazy-loaded individually via loadComponent() instead of requiring a full module via loadChildren(). NgModule-based components must be declared in exactly one module and rely on that module's imports for their dependencies. Standalone is the default since Angular 17. NgModules still work and are useful for library packaging and grouping large feature areas with shared providers.
Was this helpful?
06
When should I use canActivate vs canMatch route guards?
canActivate runs after the route matches but before activation — the lazy module code may already be downloaded. canMatch runs before route matching — if it returns false, the route is skipped entirely and the lazy module is never downloaded. Use canMatch for role-based access to lazy modules so unauthorised users never download the code. Use canActivate for auth checks on eagerly loaded routes or when the guard logic depends on route parameters.
Was this helpful?
07
How do HTTP interceptors work in Angular?
Interceptors are classes that implement the HttpInterceptor interface. Each interceptor's intercept() method receives the outgoing request and a handle() function. The interceptor can modify the request, pass it to the next interceptor via handle(), and modify or handle the response. Interceptors form a chain — the order they execute is determined by their registration order in the providers array. Common uses: auth token injection (clone request with Authorization header), global error handling (catch 401 and redirect to login), and loading state management (show/hide a spinner based on active request count).
Was this helpful?
08
What are Angular Signals and when should I use them instead of RxJS?
Signals are a reactivity primitive that wraps a value and notifies consumers when it changes. They're synchronous, have automatic dependency tracking, and integrate with Angular's change detection without the async pipe. Use signals for local component state (selected tab, form values, UI flags) and computed/derived values. Use RxJS Observables for HTTP responses, WebSocket streams, and complex async pipelines with operators like switchMap, debounceTime, and retry. Bridge between them with toSignal() (Observable to signal) and toObservable() (signal to Observable).