Vibe Engines
Visual Handbook · 2026
50 Questions · 8 Domains
Interview Preparation & Reference

50 Angular
Interview
Questions

Detailed answers, visual diagrams & interview tips — everything you need to work confidently in Angular.

Components Signals RxJS Dependency Injection Routing Forms Change Detection SSR Standalone APIs
Angular Fundamentals
Q1–7
Components & Templates
Q8–14
Services & DI
Q15–21
Routing & Navigation
Q22–27
RxJS & Observables
Q28–34
Forms & Validation
Q35–40
Change Detection
Q41–46
Advanced & Modern Angular
Q47–50
Saurabh Singh
AI Engineer & Builder.
Contents

What's
Inside

I · Fundamentals
Q1–7
Q1What is Angular, and how does it differ from AngularJS?
Q2Core building blocks of an Angular application
Q3What is a component in Angular?
Q4Main lifecycle hooks of a component
Q5NgModule vs standalone components
Q6Angular CLI & most-used commands
Q7How TypeScript improves Angular development
II · Components & Templates
Q8–14
Q8Data binding & its four types
Q9@Input & @Output decorators
Q10Structural vs attribute directives
Q11*ngIf, *ngFor, *ngSwitch & trackBy
Q12New @if, @for, @switch control flow
Q13Content projection with ng-content
Q14Template refs & ViewChild / ContentChild
III · Services & DI
Q15–21
Q15What is a service, and why use one?
Q16How Angular's dependency injection works
Q17Provider scopes: root / platform / any / component
Q18Hierarchical injectors in Angular
Q19inject() vs constructor injection
Q20InjectionTokens & when to create one
Q21Creating a singleton service safely
IV · Routing
Q22–27
Q22How the Angular Router works
Q23Route guards: CanActivate, Resolve, CanMatch
Q24Lazy loading & how to implement it
Q25Router.navigate() vs routerLink
Q26Passing data: path / query / state
Q27Nested routes & router-outlet
V · RxJS & Observables
Q28–34
Q28What is RxJS, and why does Angular use it?
Q29Observable vs Promise vs Subject
Q30Essential RxJS operators
Q31switchMap vs mergeMap vs concatMap vs exhaustMap
Q32Subjects, BehaviorSubject, ReplaySubject, AsyncSubject
Q33Preventing memory leaks from subscriptions
Q34Why the async pipe is the right default
VI · Forms
Q35–40
Q35Template-driven vs Reactive forms
Q36FormControl, FormGroup, FormArray
Q37Custom sync & async validators
Q38Typed forms (Angular 14+)
Q39Building a ControlValueAccessor
Q40Form states: dirty / touched / pristine / valid
VII · Change Detection
Q41–46
Q41How Angular's change detection works
Q42What is Zone.js & why Angular uses it
Q43ChangeDetectionStrategy.OnPush
Q44Signals — Angular's reactive primitive
Q45Zoneless change detection
Q46Performance optimization techniques
VIII · Advanced & Modern
Q47–50
Q47Angular Universal / SSR
Q48Hydration — what problem it solves
Q49Ivy renderer & how Angular changed
Q50Testing components, services & pipes
Part I

Angular Fundamentals

The vocabulary every Angular developer must know — the framework's architecture, its building blocks, and the conventions that shape every app you'll ever build.

Framework
Components
Modules
Lifecycle
Standalone APIs
TypeScript
Questions 1–7
Q1

What is Angular, and how does it differ from AngularJS?

Angular is a TypeScript-based, component-driven front-end framework maintained by Google for building scalable single-page applications. It ships with a CLI, a reactive forms library, a router, HTTP client, and a testing harness — a complete platform, not a library.

AngularJS (1.x) is the original 2010 framework. It was written in JavaScript, used two-way binding by digest cycle, centered on controllers and $scope, and had no CLI or ahead-of-time compiler. Angular 2+ was a complete rewrite in 2016 with a totally new model.

ANGULARJS (2010) VS ANGULAR (2016+)
AngularJS 1.x
JavaScript
Controllers + $scope
Digest cycle
Two-way binding default
No CLI
Legacy — out of LTS
Angular 2+
TypeScript
Components + DI
Zones / Signals
Unidirectional by default
CLI + AOT + Ivy
Modern — released 2016, still current

The two are not compatible. Angular releases a new major version roughly every 6 months; there is no upgrade path from AngularJS short of rewriting the app.

→ Interview Tip
Say "Angular" for anything 2+ and "AngularJS" only for 1.x. Mixing the names signals you don't know the lineage. Bonus points for knowing Ivy (2020) replaced the View Engine compiler.
Q2

What are the core building blocks of an Angular application?

An Angular app is a tree of components, wired together by services that the dependency injection system hands out. Templates, directives, pipes, and the router glue them together.

ANGULAR APPLICATION ANATOMY
01 · Components
A TypeScript class + template + styles. The UI atom.
02 · Templates
HTML extended with bindings, directives, and pipes.
03 · Directives
Behavior attached to DOM — structural (*ngIf) or attribute.
04 · Services
Injectable classes for business logic, HTTP, and state.
05 · Pipes
Pure transformations in templates — date, currency, async.
06 · Modules / Standalones
The unit of packaging — NgModule or standalone component.

Layered on top: the Router maps URLs to components, the HttpClient fetches data, and the Forms library handles user input. Every app also has an entry pointbootstrapApplication() in modern Angular, or platformBrowserDynamic().bootstrapModule() in the legacy NgModule world.

→ Mental Model
Think of Angular as LEGO: components are the bricks, services are the reusable parts you plug between them, and the DI system is the invisible glue that connects everything at runtime.
Q3

What is a component in Angular?

A component is a TypeScript class decorated with @Component that controls a patch of the DOM. Each component has three inseparable parts: a template (HTML), a class (logic and state), and styles (CSS/SCSS, scoped by default).

@Component({ selector: 'app-user-card', standalone: true, template: `<h2>{{ name() }}</h2>`, styles: `h2 { color: teal; }` }) export class UserCardComponent { name = input.required<string>(); }

Components form a tree: the root component (e.g., AppComponent) contains children, which contain grandchildren, and so on. They communicate downward via inputs, upward via outputs, and sideways via shared services.

Every component's selector becomes an HTML tag you can place in any parent template — that's how you compose a UI.

→ Key Insight
A component is three files that act like one: logic, markup, and styles. The class exists only to serve the template — if a method isn't called from the template or a lifecycle, it probably doesn't belong on the component.
Q4

What are the main lifecycle hooks of an Angular component?

Angular calls specific methods on your component at well-defined moments. Implementing the matching interface (OnInit, OnDestroy, etc.) lets you hook in.

LIFECYCLE HOOKS — BIRTH TO DEATH
🌱
Step 01
constructor
DI runs
📥
Step 02
ngOnChanges
inputs set
🚀
Step 03
ngOnInit
init logic
👁️
Step 04
ngAfterViewInit
DOM ready
🔄
Step 05
DoCheck / AfterView Checked
each CD tick
💀
Step 06
ngOnDestroy
cleanup
HookFires whenTypical use
ngOnChangesAny @Input changes (including first)React to parent-provided data
ngOnInitOnce, after first ngOnChangesInitial HTTP calls, form setup
ngAfterViewInitAfter view + children renderedMeasure DOM, init 3rd-party libs
ngDoCheckEvery change-detection runCustom dirty-checking (rare)
ngOnDestroyBefore component is removedUnsubscribe, clear timers

In modern Angular you'll also see afterNextRender and afterRender — newer primitives for DOM work that play nicely with SSR.

→ Interview Tip
Never do HTTP in the constructor — at that point inputs aren't set and testing is painful. Do it in ngOnInit. And if you subscribe in ngOnInit, either unsubscribe in ngOnDestroy, use takeUntilDestroyed(), or prefer the async pipe.
Q5

What is the role of NgModule, and what are standalone components?

Historically, every Angular app had one or more @NgModule classes. An NgModule was a container that declared its components, imported dependencies, and provided services — think of it as a manifest that told Angular what belongs together.

In Angular 14, standalone components arrived, and by Angular 17 they became the default. A standalone component imports what it needs directly, with no wrapping module required.

NGMODULE VS STANDALONE COMPONENT
NgModule (legacy)
@NgModule wraps components
declarations + imports + exports
Module-level providers
More boilerplate
Still supported — but no longer the default
Standalone (v14+)
standalone: true on component
imports array on the component
bootstrapApplication() entry
Tree-shakable, simpler lazy loading
The default in new apps

Directives and pipes can also be standalone. In Angular 19+ components are standalone by default — you don't even need to write standalone: true anymore.

→ Mental Model
Standalone components are ES modules for UI: each one declares its own imports. NgModules tried to solve the same problem at the framework level and added ceremony. Greenfield code should always be standalone.
Q6

What is the Angular CLI, and what are its most-used commands?

The Angular CLI (ng) is the official command-line tool. It scaffolds projects, generates files, runs a dev server, bundles for production, and runs tests — all on top of esbuild (or Webpack, historically).

CommandWhat it does
ng new my-appCreate a new Angular workspace with everything wired up.
ng serveDev server at localhost:4200 with hot reload.
ng buildProduction bundle into dist/.
ng generate component|service|pipeScaffold files (shorthand: ng g c name).
ng testRun unit tests (Karma/Jasmine or Vitest).
ng updateBump Angular versions with automated migrations (schematics).
ng lintRun ESLint against the workspace.
ng add @angular/materialInstall & configure a library in one step.

The killer feature is schematics: ng update not only upgrades dependencies, it also rewrites your code to match the new API — automating migrations that would otherwise take days.

→ Real-World Use
Never edit a generated file "just because". Keep the CLI's configuration canonical — it's what ng update expects. Custom Webpack configs block you from taking migrations cleanly.
Q7

How does TypeScript improve Angular development over plain JavaScript?

TypeScript is a statically-typed superset of JavaScript. Angular was built in TypeScript and leans on it for almost everything — from templates to dependency injection.

TYPESCRIPT → ANGULAR LEVERAGE
01 · Compile-time safety
Catches type mismatches, typos, and missing properties before the code ships.
02 · Decorators
@Component, @Injectable, @Input are TS decorators — Angular metadata lives here.
03 · DI from types
Constructor parameter types tell Angular what to inject. No XML or config needed.
04 · Template type checking
Strict mode type-checks HTML bindings too — not just .ts code.

In strictTemplates mode, Angular even checks your HTML: if you bind user.firstname but the model has firstName, the build fails. That's not something you get in a JavaScript framework.

→ Interview Tip
Keep strict: true on in tsconfig — it turns on strict null checks, template checks, and noImplicitAny. Teams that disable strict mode almost always regret it within six months.
Part II

Components & Templates

The building-block mechanics: bindings that keep data and DOM in sync, decorators for communication, directives that reshape the DOM, and the new control-flow syntax.

Data binding
@Input / @Output
Directives
Control flow
Content projection
ViewChild
Questions 8–14
Q8

What is data binding, and what are the four types in Angular?

Data binding is the syntax that keeps your component class and its template in sync. Angular has four kinds, organized by direction.

TypeSyntaxDirectionExample
Interpolation{{ value }}Class → DOM text<h1>{{ title }}</h1>
Property binding[prop]="value"Class → DOM property<img [src]="url">
Event binding(event)="handler()"DOM → Class<button (click)="save()">
Two-way[(ngModel)]="x"Both<input [(ngModel)]="name">
THE "BANANA IN A BOX" — [( )] DESUGARED
Shorthand
[(ngModel)]="name"
One binding
Equivalent
[ngModel]="name"
(ngModelChange)="name=$event"
Two bindings under the hood

Two-way binding only works when the directive exposes an @Output named xChange to match the @Input named x. That's the contract — Angular sugars the rest.

→ Interview Tip
Default to one-way flow: inputs down, events up. Reserve two-way binding for form inputs. A codebase with [(x)] everywhere is usually a codebase with change-detection bugs.
Q9

What are @Input and @Output decorators, and how do they enable component communication?

@Input marks a class property as "data that flows in from the parent". @Output marks an EventEmitter that fires events out to the parent. Together they implement Angular's unidirectional data-flow model.

// child.component.ts export class ChildComponent { @Input() user!: User; @Output() saved = new EventEmitter<User>(); save() { this.saved.emit(this.user); } } // parent template: <app-child [user]="currentUser" (saved)="onSaved($event)"></app-child>

In Angular 17.1+, the newer input() and output() signal-based APIs replace the decorators. input() returns a readable signal; input.required() makes the input mandatory; model() is the new two-way binding primitive.

Modern style (v17.1+): user = input.required<User>(); — read as user(), reactive by construction, plays nicely with OnPush and zoneless.

→ Interview Tip
Know both APIs. New code should use input() / output() signals. Old code will use @Input / @Output — interviewers test whether you can work in both dialects without confusing them.
Q10

What is the difference between structural and attribute directives?

A directive is a class with @Directive that attaches behavior to an element. Angular has three flavors; the most interesting distinction is structural vs attribute.

STRUCTURAL VS ATTRIBUTE DIRECTIVES
Structural
Changes the DOM layout
Prefixed with *
*ngIf / *ngFor / *ngSwitch
Adds or removes elements
Attribute
Changes appearance or behavior
Looks like a regular attr
ngClass / ngStyle / custom
Modifies the element in place

The leading * in a structural directive is sugar. *ngIf="show" desugars to an <ng-template> wrapper, and Angular stamps it out only when the condition is true. That's why the element literally doesn't exist in the DOM when show is false.

The third flavor is the component itself — a special directive with a template. All three share the same DI and lifecycle.

→ Key Insight
You can only have one structural directive per element. Nest an <ng-container> to combine them — it renders no DOM but hosts a directive.
Q11

How do *ngIf, *ngFor, and *ngSwitch work, and what are trackBy functions?

*ngIf stamps a template into the DOM only when the expression is truthy, and tears it down when it turns falsy. *ngFor instantiates the template once per item of an iterable. *ngSwitch picks one branch from several like a switch statement.

<!-- *ngFor with trackBy prevents DOM thrash --> <li *ngFor="let user of users; trackBy: trackById"> {{ user.name }} </li> trackById(index: number, u: User) { return u.id; }

By default, *ngFor identifies items by object identity. If you replace the whole array (common with immutable state), Angular destroys and rebuilds every node — expensive for long lists. trackBy returns a stable key per item so Angular can move nodes instead of re-creating them.

In Angular 17+ the @for block takes a mandatory track expression — Angular forces you to think about identity from the start.

→ Real-World Use
Always trackBy by a stable ID, never by index. Tracking by index looks fine until you filter or sort — then every DOM node is recreated because its "identity" just changed.
Q12

What is the new @if, @for, @switch control flow syntax in Angular 17+?

Angular 17 introduced built-in control flow as a first-class template language feature. No directives, no imports — it compiles directly and is faster and smaller than the old *ngIf equivalents.

@if (user) { <p>Hello {{ user.name }}</p> } @else if (loading) { <p>Loading…</p> } @else { <p>No user</p> } @for (item of items; track item.id) { <li>{{ item.name }}</li> } @empty { <p>Nothing yet</p> } @switch (status) { @case ('ok') { <span>Ready</span> } @case ('err') { <span>Failed</span> } @default { <span>…</span> } }
*NGIF VS @IF — WHY THE CHANGE
Old — structural directive
Requires CommonModule import
Awkward else/then syntax
Slower — runs as directive
Still works — legacy
New — block syntax
Zero imports
@empty / @else / @case built-in
~90% faster in benchmarks
Preferred going forward

Angular ships a migration schematic — ng generate @angular/core:control-flow converts a whole codebase automatically.

→ Key Insight
The track expression in @for is required — Angular wouldn't let it compile without one, because they wanted to fix a category of bug (forgetting trackBy) at the language level.
Q13

What is content projection (ng-content), and when would you use it?

Content projection lets a component accept markup from its caller and slot it into its own template. It's Angular's version of React's children prop or Vue's <slot>.

<!-- card.component.html --> <div class="card"> <header><ng-content select="[slot=title]"></ng-content></header> <div><ng-content></ng-content></div> </div> <!-- usage --> <app-card> <h2 slot="title">Hello</h2> <p>This is the body</p> </app-card>

Use it whenever you're building layout components: cards, modals, tabs, expandable panels. The outer component owns structure and chrome; the caller owns content. This is the foundation of reusable UI kits.

Multiple slots are possible via select — a CSS selector that routes specific children to specific <ng-content> spots.

→ Mental Model
If your component is about layout, not data, reach for ng-content. If you're tempted to pass a long @Input of HTML-as-string, you want projection instead — safer, typed, and observable.
Q14

What are template reference variables, and what are ViewChild / ContentChild?

A template reference variable is a named handle to a DOM element or directive, created with the # prefix. You can use it anywhere else in the same template.

<input #nameInput> <button (click)="console.log(nameInput.value)">Log</button>

@ViewChild gets a reference from within your own template — the component's view. @ContentChild gets one from content projected in via <ng-content>. Both are the class-level, programmatic version of #.

WHERE EACH QUERY LOOKS
@ViewChild
Own template
Resolves after ngAfterViewInit
Measure your own DOM, init 3rd-party libs
@ContentChild
Projected content
Resolves after ngAfterContentInit
Access children caller passed in

Angular 17.2 added the signal-based viewChild() and contentChild() functions. They return a reactive signal, making timing issues ("it was undefined") mostly disappear.

→ Interview Tip
The classic bug: querying a @ViewChild inside ngOnInit and getting undefined. Views aren't rendered yet — use ngAfterViewInit. The signal API (viewChild()) makes this self-correcting.
Part III

Services & Dependency Injection

Angular's superpower. A small, hierarchical DI system lets components stay thin while business logic, HTTP, and state live in reusable, testable services.

@Injectable
providedIn
Hierarchical injectors
inject()
InjectionToken
Singletons
Questions 15–21
Q15

What is a service in Angular, and why would you use one?

A service is a plain TypeScript class marked @Injectable(). It's where the logic that isn't part of "what the screen looks like" lives — HTTP calls, shared state, auth, logging, caching, business rules.

@Injectable({ providedIn: 'root' }) export class UserService { private http = inject(HttpClient); getUser(id: string) { return this.http.get<User>(`/api/users/${id}`); } }

Why bother? Three reasons:

WHY PUT LOGIC IN A SERVICE
01 · Reuse
One service, many components. Don't repeat HTTP logic in every screen.
02 · Testability
DI makes services trivial to mock in unit tests.
03 · Separation
Components stay about UI. Services stay about logic.
→ Mental Model
If a component is longer than ~150 lines, something belongs in a service. Services are where logic goes to become testable — components should mostly be presentation.
Q16

How does Angular's dependency injection system work?

Dependency Injection (DI) is the pattern where a class asks for its dependencies instead of creating them. Angular reads the type signature (or inject() call), looks up a registered provider, and hands back an instance.

DI FLOW — FROM TOKEN TO INSTANCE
🧩
Step 01
Token
class or InjectionToken
📚
Step 02
Injector
looks up provider
🏭
Step 03
Factory
runs / caches
📦
Step 04
Instance
handed to consumer

Three parts to remember:

ConceptWhat it is
TokenThe key to look up — usually a class, sometimes an InjectionToken<T>.
ProviderThe recipe — useClass, useValue, useFactory, or useExisting.
InjectorThe registry. There's a tree of them matching the component tree.

The result: your classes never say new HttpClient(). They ask for an HttpClient and Angular decides what to give them — the real one in production, a mock in tests.

→ Key Insight
DI is why Angular code is so testable: swap the provider in TestBed, and every consumer automatically uses the mock. That's the real reason the framework insists on this pattern.
Q17

What are the provider scopes: 'root', 'platform', 'any', and component-level?

When you write @Injectable({ providedIn: 'root' }), you're telling Angular where to register the service. The scope determines how many instances you get.

ScopeLifetimeUse for
'root'App-wide singleton99% of services: auth, HTTP wrappers, state
'platform'Shared across multiple apps on the pageMicro-frontends
'any'One per lazy-loaded moduleModule-scoped caches
Component-levelOne per component instancePer-form state, per-widget models
SCOPE VISUALIZED
Platform injector
Shared by all Angular apps on the page
Root injector (providedIn: 'root')
One instance per app
Route / lazy injector
Created for lazy routes ("any" lives here)
Component injector
One per component instance
→ Interview Tip
Default to providedIn: 'root'. It's tree-shakable — if nothing injects the service, it's removed from the bundle. Module-level providers can't do that.
Q18

What are hierarchical injectors in Angular?

Angular has a tree of injectors that mirrors the component tree. When a class asks for a dependency, Angular starts at that component's injector and walks upward until it finds a provider or hits the root.

This means you can shadow a service at a lower level. Provide LoggerService on a specific component, and that component and its children get the local one — while the rest of the app keeps the global one.

INJECTOR LOOKUP WALKS UP THE TREE
AppComponent (root)
provides: AuthService
↓ child
DashboardComponent
provides: DashboardStore (local)
↓ child
WidgetComponent
inject(DashboardStore) → local
inject(AuthService) → walks up to root

Resolution modifiers tune this: @Self() only looks locally, @SkipSelf() skips the current injector, @Optional() returns null if missing, @Host() stops at the host component.

→ Real-World Use
Component-level providers are the best way to build scoped state — one store per wizard instance, one form-state service per dialog — without leaking data between concurrent instances.
Q19

What is the inject() function, and how does it differ from constructor injection?

Historically, Angular DI went through the constructor: constructor(private http: HttpClient) {}. Since Angular 14 the function inject() is the modern alternative — you call it inside a class field, function, or factory to get a dependency.

// Old export class UserService { constructor(private http: HttpClient) {} } // Modern export class UserService { private http = inject(HttpClient); }
CONSTRUCTOR VS inject()
Constructor
Works everywhere
Inheritance is awkward
Can't call in helpers
Default since Angular 2
inject()
Clean inheritance
Works in functional guards / interceptors
Must run in an injection context
Modern, recommended

inject() must be called in an injection context: during class construction, inside a runInInjectionContext() block, or from functional guards/resolvers/interceptors. Call it from a random method and you'll get an error.

→ Interview Tip
The functional guards and interceptors introduced in Angular 14+ only work with inject() — there's no constructor to put a parameter in. Knowing this signals you've kept up with modern Angular.
Q20

What are InjectionTokens, and when would you create one?

A class can serve as its own DI token — Angular looks it up by identity. But sometimes you need to inject something that isn't a class: a string, a config object, a factory, or an abstract interface. That's what InjectionToken is for.

export const API_URL = new InjectionToken<string>('API_URL'); // provide in main.ts bootstrapApplication(App, { providers: [{ provide: API_URL, useValue: 'https://api.example.com' }] }); // inject anywhere const url = inject(API_URL);

The class would work for an instance, but not for a primitive — TypeScript interfaces are erased at runtime, so you can't use them as keys either. InjectionToken<T> fills both gaps.

→ Real-World Use
Environment config, feature flags, API URLs, and "replace this strategy from outside" hooks all belong in an InjectionToken. Never hard-code a base URL in a service.
Q21

How do you create a singleton service, and what are the pitfalls?

The one-liner: @Injectable({ providedIn: 'root' }). Angular creates one instance per application injector and hands the same one to everyone who asks.

COMMON WAYS TO ACCIDENTALLY GET TWO INSTANCES
Pitfall · Lazy module re-provides
Adding the service to a lazy module's providers creates a new instance for that module.
Pitfall · Component providers
Listing the service in a component's providers makes a new one per component instance.
Pitfall · Forgotten imports
Two .ts files importing from different relative paths of the same file — bundler treats them as two modules.
Pitfall · SSR double-run
Server and client each have their own root injector — don't assume "singleton" means "shared between SSR and browser".

Rule of thumb: put providedIn: 'root' on the service, and never list it in a providers array. If you do both, you'll get multiple instances and race conditions.

→ Key Insight
"Singleton" in Angular means "one per injector", not "one per process". Each injector up the tree can have its own copy. When state goes weird, check where the service is actually being provided.
Part IV

Routing & Navigation

How Angular turns URLs into rendered views — lazy loading, guards, resolvers, and the primitives that make multi-screen apps possible without a full page reload.

Router
Guards
Lazy loading
routerLink
Route params
Nested routes
Questions 22–27
Q22

How does the Angular Router work?

The Router is a service that maps URLs to component trees. You declare a config of Routes; the router watches the URL, matches against the config, builds the matching tree, and renders it into <router-outlet>.

NAVIGATION LIFECYCLE
🔗
Step 01
URL change
click or navigate()
🧭
Step 02
Match route
walk config
🛡️
Step 03
Run guards
CanMatch / CanActivate
📦
Step 04
Resolve data
resolvers fetch
🎨
Step 05
Activate
render outlet
export const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'users/:id', component: UserDetailComponent }, { path: 'admin', loadChildren: () => import('./admin/routes') } ];

You bootstrap it with provideRouter(routes) in standalone apps, or RouterModule.forRoot(routes) in NgModule ones.

→ Mental Model
Routes are a tree, not a flat list. The URL is parsed segment by segment, each matching a level of the tree. That's why nested <router-outlet>s work naturally.
Q23

What are route guards (CanActivate, CanDeactivate, Resolve, CanMatch)?

Guards are functions (modern) or classes (legacy) that the router invokes at specific phases of navigation. They return a boolean, a UrlTree, or an Observable/Promise of one — and they can veto, redirect, or enrich a navigation.

GuardFiresTypical use
CanMatchBefore a route is even consideredFeature flags, role-based route variants
CanActivateBefore activating a routeAuth: "must be logged in"
CanActivateChildBefore any child activatesNested auth blocks
CanDeactivateBefore leaving a route"You have unsaved changes"
ResolveAfter guards, before renderPre-fetch data so the view never flashes empty
// functional CanActivate (modern) export const authGuard: CanActivateFn = () => { const auth = inject(AuthService); const router = inject(Router); return auth.isLoggedIn() || router.createUrlTree(['/login']); };
→ Interview Tip
Prefer CanMatch over CanActivate when you want to pick a different route entirely based on state (e.g., admins see admin routes, users don't even see them exist).
Q24

What is lazy loading, and how do you implement it?

Lazy loading means the code for a route is only downloaded when the user navigates to it. This keeps the initial bundle small and the first paint fast.

// Lazy-load a single standalone component { path: 'settings', loadComponent: () => import('./settings.component').then(m => m.SettingsComponent) } // Lazy-load a whole route tree { path: 'admin', loadChildren: () => import('./admin/routes').then(m => m.ADMIN_ROUTES) }
BUNDLE SIZE — EAGER VS LAZY
Eager
1.8 MB initial
Lazy
600 KB initial + chunks on demand

Angular also supports preloading strategiesPreloadAllModules loads lazy chunks in the background after the app boots, giving you the speed of eager loading with the tiny initial bundle of lazy.

→ Real-World Use
Lazy-load by feature route, not by component. Preload the likely-next route with QuicklinkStrategy from ngx-quicklink to get near-instant navigation without bloating the first paint.
Q25

What is the difference between Router.navigate() and routerLink?

Both trigger navigation but at different layers. routerLink is a template directive — declarative, ideal for links and buttons. Router.navigate() is a programmatic API — use it from code when navigation is triggered by logic, not a click.

WHEN TO USE EACH
routerLink
Template
Renders <a href> correctly
SEO + right-click friendly
Use for <a> and obvious nav buttons
Router.navigate()
TypeScript
Returns a Promise
Works after HTTP / guards
Use after login, save, or any async flow
// template <a routerLink="/users" [queryParams]="{page:2}">Users</a> // code this.router.navigate(['/users', user.id], { queryParams: { edit: true } });
→ Interview Tip
Never just location.href = '/foo' in an Angular app — you lose state, you do a full page reload, and the router never runs its guards. Always go through the Router.
Q26

How do you pass data between routes using path params, query params, and state?

The Router offers three channels, each with different semantics.

ChannelExampleShows in URLTypical use
Path params/users/:idYesIdentifies the resource
Query params/users?page=2YesFilters, sort, pagination
State(opaque, in history)NoTransient data, e.g., form draft on redirect
// read in the component const route = inject(ActivatedRoute); const id$ = route.paramMap.pipe(map(p => p.get('id'))); const page$ = route.queryParamMap.pipe(map(q => q.get('page'))); // state comes via navigation extras this.router.navigate(['/review'], { state: { draft } }); const s = history.state;

Modern Angular also exposes withComponentInputBinding() — when enabled, path/query params are bound directly to @Input()s on the routed component. No ActivatedRoute subscription needed.

→ Mental Model
Path = identity, query = view options, state = invisible one-shot. Use path for "what am I looking at", query for "how am I filtering it", state for "don't show in URL but survive F5… no, actually state doesn't survive reload — so only use it for one-shot hand-offs".
Q27

What are nested routes, and what is the role of router-outlet?

A route can have children. When you nest routes, each parent component hosts a <router-outlet>, and the matching child renders inside it. This lets you build layouts with persistent chrome — think an admin shell with a sidebar, where only the right pane changes per route.

export const routes: Routes = [ { path: 'admin', component: AdminShellComponent, // renders sidebar + <router-outlet> children: [ { path: 'users', component: UserListComponent }, { path: 'users/:id', component: UserDetailComponent }, { path: '', redirectTo: 'users', pathMatch: 'full' } ] } ];
NESTED OUTLETS IN ACTION
AppComponent · <router-outlet>
Top-level — shows AdminShellComponent for /admin/*
AdminShellComponent · <router-outlet>
Renders UserListComponent or UserDetailComponent inside

You can also have named outlets for modal or secondary content: <router-outlet name="aside"> combined with outlet: 'aside' in the route config.

→ Key Insight
Nested routes are not optional — they're how you avoid re-rendering the whole layout on every navigation. Big app, multiple sections, persistent sidebar? You want nested routes.
Part V

RxJS & Observables

Angular's async model. Once you understand Observables, Subjects, and the four flattening operators, the HTTP client, Router, and Forms all become one consistent story.

Observable
Operators
Subjects
switchMap family
async pipe
Memory leaks
Questions 28–34
Q28

What is RxJS, and why does Angular rely heavily on Observables?

RxJS (Reactive Extensions for JavaScript) is a library of Observables — lazy, multi-value, cancellable async streams — plus a rich set of operators to transform them. Angular adopted it as its default async model.

Almost every async surface in Angular returns an Observable: HttpClient.get(), route params, form value changes, Router.events. That consistency is the payoff — you learn one model, and it applies everywhere.

CALLBACK → PROMISE → OBSERVABLE
ModelValuesLazy?Cancel?
CallbackPush, 0–NManual
PromisePull, 0–1No (eager)No
ObservablePush, 0–NYesYes (unsubscribe)

Two properties matter most: Observables are lazy (nothing runs until you subscribe), and they're cancellable (unsubscribe aborts the work). Promises are neither — a Promise fires the moment you create it and can't be stopped.

→ Interview Tip
Signals don't replace RxJS — they coexist. Use signals for synchronous UI state; use Observables for async streams (HTTP, WebSockets, typeahead). Angular provides toSignal() and toObservable() to bridge them.
Q29

What is the difference between Observable, Promise, and Subject?

All three are async primitives, but they solve different problems.

PromiseObservableSubject
ValuesOneZero to manyZero to many
LazyNo — runs on creationYes — on subscribeActive — you push to it
MulticastYes (resolved once)Default: unicastYes — broadcasts to all subs
CancellableNoYes — unsubscribeYes — unsubscribe
Use forOne-shot request you can't cancelStreams, HTTP, eventsCross-component event bus, state

A Subject is both an Observable and an Observer. You can subject.next(x) to push a value, and any subscriber receives it. This makes Subjects useful as event buses and as the core of simple state stores.

→ Key Insight
Think "unicast vs multicast". An Observable creates a fresh producer per subscriber (re-runs the HTTP call if two subscribe). A Subject shares one producer. Subjects are sharing; Observables are creation.
Q30

What are the essential RxJS operators?

RxJS has 100+ operators but you'll use the same dozen 90% of the time.

CategoryOperatorWhat it does
TransformmapMaps each value through a function.
scanReduce but emits each intermediate — state accumulator.
FilterfilterDrops values that don't match.
distinctUntilChangedSkips repeats.
debounceTime / throttleTimeRate-limits emissions (typeahead!).
CombinecombineLatestCombine latest values from multiple streams.
forkJoinLike Promise.all — waits for all to complete.
FlattenswitchMap / mergeMap / concatMapMap each value to another Observable and flatten.
ErrorcatchErrorHandle failures; return a fallback stream.
retry(n)Resubscribe on error up to n times.
UtilitytapSide effects (logging) without changing values.
takeUntilDestroyedUnsubscribe when component is destroyed.
search$.pipe( debounceTime(300), distinctUntilChanged(), switchMap(q => api.search(q)), catchError(() => of([])), takeUntilDestroyed() ).subscribe(results => this.results = results);
→ Mental Model
Learn operators in groups of intent: transform, filter, combine, flatten, error, side-effect. Once you can name the intent, finding the operator on rxjs.dev takes seconds.
Q31

What is the difference between switchMap, mergeMap, concatMap, and exhaustMap?

All four take a value, map it to an inner Observable, and flatten the result. The difference is how they handle overlap — what to do when a new outer value arrives while the previous inner is still running.

OperatorOverlap behaviorClassic use
switchMapCancels the previous inner, starts the new oneTypeahead search — only latest matters
mergeMapRuns all in parallel, merges emissionsIndependent parallel requests
concatMapQueues — runs one at a time, in orderOrdered writes, "save one at a time"
exhaustMapIgnores new outer values while inner runs"Don't double-submit the login form"
FOUR OUTER EMISSIONS, SAME INNER LATENCY
switch
only last completes
merge
all 4 overlap
concat
queue, sequential
exhaust
only first runs, rest ignored

Mnemonic: switch-cancel, merge-parallel, concat-queue, exhaust-debounce.

→ Interview Tip
This question is a staple. If asked "user types fast in a search box", answer switchMap and explain why: old requests don't matter, and they shouldn't win a race by arriving late.
Q32

What are Subjects, BehaviorSubject, ReplaySubject, and AsyncSubject?

All four are Subjects — multicasting Observables you can push values into. The difference is what a late subscriber sees.

TypeLate subscriber seesTypical use
SubjectOnly future emissionsPure event bus
BehaviorSubjectThe current value immediately, then futureState store — always has "current"
ReplaySubject(n)Last n values, then futureChat history, cache of last N
AsyncSubjectOnly the final value, on completeCache of a one-shot async result
// simple state store class UserStore { private _user$ = new BehaviorSubject<User | null>(null); user$ = this._user$.asObservable(); set(u: User) { this._user$.next(u); } get current() { return this._user$.value; } }

BehaviorSubject is by far the most common — it's the building block of lightweight state stores, since it always has a "current" value you can synchronously read.

→ Real-World Use
Expose Subjects as .asObservable() so callers can't .next() on them from outside. That's how you keep state writes centralized — readers subscribe, writers call dedicated methods.
Q33

How do you prevent memory leaks from subscriptions?

Subscriptions keep the Observable chain alive. If a component is destroyed but its subscription isn't, both the chain and any referenced DOM/state stay in memory — a leak that grows with every navigation.

FOUR WAYS TO UNSUBSCRIBE CLEANLY
01 · async pipe
Template pipe subscribes + unsubscribes for you. Best default.
02 · takeUntilDestroyed()
Angular 16+: ties lifetime to the component. One-liner in pipe().
03 · takeUntil(destroy$)
Older pattern: Subject fires in ngOnDestroy.
04 · Manual
Save the Subscription, call .unsubscribe() in ngOnDestroy. Error-prone.
// modern, idiomatic — Angular 16+ ngOnInit() { this.api.getUsers().pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(users => this.users = users); }
→ Interview Tip
Default to async pipe in templates. When you must subscribe in code, use takeUntilDestroyed(). Manual .unsubscribe() in ngOnDestroy is last-resort — one forgotten subscription and you have a leak.
Q34

What is the async pipe, and why should you prefer it over manual subscriptions?

The async pipe (| async) subscribes to an Observable (or Promise), returns the latest value for rendering, and unsubscribes when the view is destroyed. It's the single highest-leverage template feature in Angular.

<ul> @for (user of users$ | async; track user.id) { <li>{{ user.name }}</li> } </ul>
WHY ASYNC PIPE IS THE RIGHT DEFAULT
Manual subscribe
You must remember to unsubscribe. You mutate a class field. OnPush doesn't know when to update.
async pipe
No leak. No class-level mutation. Triggers change detection precisely — works with OnPush out of the box.

The pipe also composes well. Need to transform before rendering? Put operators in the pipe() on the Observable — the template stays declarative.

→ Mental Model
If a piece of state is from an Observable, pipe it into the template. Don't subscribe, assign to this.x, and re-render. The first approach is declarative; the second is a bug waiting to happen.
Part VI

Forms & Validation

Angular has two form systems. Knowing when to pick each, and how reactive forms model complex UIs, is the difference between fighting the framework and letting it help.

Reactive vs Template
FormGroup
Validators
Typed forms
ControlValueAccessor
Form states
Questions 35–40
Q35

What is the difference between Template-driven and Reactive forms?

Angular has two form systems. They do the same job but use opposite philosophies.

TEMPLATE-DRIVEN VS REACTIVE
Template-driven
Form lives in HTML
Uses ngModel + directives
FormsModule
Async model construction
Great for small static forms
Reactive
Form lives in TS
new FormGroup(...)
ReactiveFormsModule
Synchronous model
Scales to complex / dynamic forms
Template-drivenReactive
Source of truthHTML directivesTypeScript FormGroup
Unit testabilityNeeds DOMPure function
Dynamic fieldsAwkwardTrivial — formArray.push()
TypedNoYes (Angular 14+)
DebuggingInspect DOMInspect a plain object

For any form with more than a few fields, conditional rules, or dynamic controls — use Reactive. Template-driven is fine for a login form or a search box.

→ Interview Tip
Saying "I always use reactive forms because they're testable and typed" is a safe default answer. Then show you know when template-driven is acceptable — small, static, never going to grow.
Q36

What are FormControl, FormGroup, and FormArray?

These are the three building blocks of reactive forms. They compose into any form shape you need.

ClassRepresentsExample
FormControlA single input's value + stateOne text field
FormGroupA named object of controlsA user form with name, email, age
FormArrayAn indexed list of controlsA dynamic list of phone numbers
const fb = inject(FormBuilder); form = fb.group({ name: fb.control('', Validators.required), email: fb.control('', [Validators.required, Validators.email]), phones: fb.array([fb.control('')]) }); addPhone() { (this.form.get('phones') as FormArray).push(this.fb.control('')); }
A FORM IS A TREE OF CONTROLS
FormGroup: userForm
root — emits value/status change as children change
FormControl: name, email
FormArray: phones → FormControls

All three extend AbstractControl, which gives them value, status, valueChanges, statusChanges, and validators.

→ Real-World Use
Use FormBuilder (fb.group / fb.control / fb.array) — it's dramatically less noisy than new FormControl() everywhere and produces identical objects.
Q37

How do you implement custom synchronous and asynchronous validators?

A sync validator is a function (c: AbstractControl) => ValidationErrors | null. An async validator returns a Promise or Observable of the same.

// sync — must include a digit export function mustIncludeDigit(c: AbstractControl): ValidationErrors | null { return /\d/.test(c.value) ? null : { mustIncludeDigit: true }; } // async — username availability export function usernameAvailable(api: UserApi): AsyncValidatorFn { return c => api.check(c.value).pipe( debounceTime(400), map(exists => exists ? { taken: true } : null), first() ); }

Attach them when constructing the control:

username: fb.control('', { validators: [Validators.required, mustIncludeDigit], asyncValidators: [usernameAvailable(api)], updateOn: 'blur' });

Two rules: async validators only run after sync validators pass, and they should emit once and complete (use first()) — otherwise the control stays in PENDING forever.

→ Key Insight
Use updateOn: 'blur' for fields with async validators. Revalidating on every keystroke hammers your API and makes the form feel laggy.
Q38

What are typed forms (Angular 14+)?

Before v14, FormControl.value was typed as any. Every access was unsafe — a typo in a control name silently returned undefined. Typed forms fix this by inferring the shape of the form from its structure.

const form = fb.group({ name: fb.control('', { nonNullable: true }), age: fb.control<number>(0, { nonNullable: true }) }); // value is typed: { name: string; age: number } const v = form.getRawValue(); // ✓ typed form.get('naem'); // ✗ compile error — no such control

Nullable vs non-nullable: by default, calling control.reset() sets the value to null, so the value type is string | null. Pass nonNullable: true to make reset restore the initial value and keep the type clean.

Use FormBuilder.nonNullable for a whole form: fb.nonNullable.group({...}) — every control becomes non-nullable automatically.

→ Interview Tip
If a team is on Angular 14+ and still uses the untyped UntypedFormGroup shims, it's a sign nobody migrated. Typed forms are the modern baseline — expect them in greenfield code.
Q39

How do you build a custom form control using ControlValueAccessor?

A ControlValueAccessor (CVA) is the contract that lets your component participate in Angular forms as if it were an <input>. Implement the interface and register the component as a value accessor, and you get to use formControlName, ngModel, validation states — everything.

@Component({ selector: 'app-rating', template: `...`, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RatingComponent), multi: true }] }) export class RatingComponent implements ControlValueAccessor { value = 0; onChange = (v: number) => {}; onTouched = () => {}; writeValue(v: number) { this.value = v; } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } setDisabledState?(isDisabled: boolean) { /* ... */ } }

Now you can write <app-rating formControlName="score"> and it behaves like a native input. The parent form doesn't care how the UI is implemented — it's a sealed black box that reads and writes a value.

→ Real-World Use
Every rich input in a design system (date pickers, rating stars, tag inputs, rich text editors) should be a CVA. That's what keeps the form API uniform across custom and native controls.
Q40

What are form states (dirty, touched, pristine, valid), and when do they change?

Each AbstractControl exposes a handful of boolean flags describing its state. They drive styling (ng-dirty classes) and logic (only submit if valid, etc.).

FlagTrue whenOpposite
pristineUser hasn't changed the valuedirty
dirtyUser changed the value (even back to original)pristine
untouchedUser hasn't blurred the fieldtouched
touchedUser blurred the field at least onceuntouched
validAll validators passinvalid
pendingAsync validation is running
disabledControl is disabled (excluded from value/validation)enabled

UX rule: show a validation error only when the control is invalid AND touched. Showing errors on an untouched, never-edited field feels hostile.

Programmatic changes via setValue / patchValue do not make a control dirty unless you also call markAsDirty(). This is usually what you want — server-fetched values shouldn't look "changed".

→ Interview Tip
"Dirty" means the user changed it. "Touched" means the user blurred it. A form can be dirty but untouched (user typed then never left the field) or pristine and touched (user tabbed through without typing).
Part VII

Change Detection & Performance

How Angular decides when to repaint the DOM — Zone.js, OnPush, Signals, zoneless. The mechanics behind every "why isn't my view updating" bug and every slow-scroll complaint.

Change detection
Zone.js
OnPush
Signals
Zoneless
Optimization
Questions 41–46
Q41

How does Angular's change detection work?

Change detection is the process of comparing component data to the DOM and updating what differs. Angular runs it in a depth-first walk of the component tree, starting at the root, checking each binding, and updating any that changed.

CHANGE DETECTION TICK
Step 01
Trigger
async event fires
🌳
Step 02
Walk tree
from root down
🔍
Step 03
Check bindings
old vs new
🎨
Step 04
Update DOM
if different

What triggers a tick? By default, anything asynchronous: a click, a setTimeout, an XHR response, a Promise resolving. Zone.js monkey-patches the browser's async APIs and notifies Angular every time one runs.

Each component's template compiles into a dirty-checking function. The comparison uses === for primitives and reference equality for objects. That's why mutating an array in place doesn't trigger updates under OnPush — the reference didn't change.

→ Mental Model
"When something async happens, check everything." That's default Angular. OnPush and Signals let you replace "check everything" with "check only what changed" — which is most of the performance story.
Q42

What is Zone.js, and what role does it play?

Zone.js is a library that patches every asynchronous browser API — setTimeout, fetch, event listeners, promises — so that Angular can be notified whenever one of them runs. A "zone" is an execution context that persists across async boundaries.

HOW ZONE.JS WIRES CHANGE DETECTION
App starts inside NgZone
Zone.js patches setTimeout, addEventListener, XMLHttpRequest, …
Async task runs inside the zone
e.g., click handler, HTTP response, timeout callback
Zone notifies Angular
onMicrotaskEmpty fires → ApplicationRef.tick()

Historically Zone.js made Angular "magical" — you could mutate any property and the view would update. The cost: extra bundle weight (~30 KB), CPU overhead, and confusing stack traces. Angular 18 made Zone.js optional via the zoneless change detection path.

You can opt part of your app out of the zone with NgZone.runOutsideAngular() — useful for hot-path operations like mouse-move or animation frames that shouldn't trigger change detection.

→ Key Insight
Zone.js's value is that "everything just works". Its cost is that "everything runs change detection". Modern Angular is moving toward explicit change-detection triggers (Signals), and away from this implicit model.
Q43

What is ChangeDetectionStrategy.OnPush, and when should you use it?

OnPush changes the rule for when a component's change detector runs. Instead of "every tick", it runs only when:

OnPush CHECKS ONLY WHEN…
01 · @Input reference changes
A new object/array comes in. Same ref, mutated, won't trigger.
02 · DOM event in the component
click, input, anything bound in its template.
03 · async pipe emits
async pipe calls markForCheck() under the hood.
04 · Explicit markForCheck()
Or a signal used in the template changes.
@Component({ selector: 'app-user', changeDetection: ChangeDetectionStrategy.OnPush, template: `...` })

With OnPush, Angular skips whole subtrees that haven't been marked dirty. On a big app this can halve or quarter CD cost. The tradeoff: your app has to be immutable — replace arrays/objects instead of mutating them.

→ Interview Tip
OnPush + immutable state + async pipe is the production recipe. Teams that turn on OnPush late hit 20 "why isn't my view updating" bugs; teams that adopt it from day one have none.
Q44

What are Signals in Angular, and how do they change state management?

Signals are a new reactive primitive introduced in Angular 16 (stable in 17). A signal holds a value; reading it tracks a dependency; writing to it automatically notifies anything that reads it.

count = signal(0); doubled = computed(() => this.count() * 2); effect(() => { console.log('count is', this.count()); }); increment() { this.count.update(c => c + 1); }
THREE PRIMITIVES
PrimitivePurpose
signal(value)Writable reactive value — read with x(), set with x.set() / x.update().
computed(fn)Derived read-only signal — recomputes when inputs change, cached otherwise.
effect(fn)Side-effect that runs when any read signal changes. Used for logging, DOM work.

Using a signal in a template makes the component's binding glitch-free reactive — Angular knows exactly which signals a view depends on and only re-renders when those change, even without OnPush.

Signals are synchronous. For async work, keep using Observables and bridge with toSignal() / toObservable().

→ Mental Model
Signals replace component-level state and BehaviorSubjects. They don't replace RxJS — use signals for the "here and now" value, Observables for streams of events over time.
Q45

What is zoneless change detection, and why was it introduced?

Zoneless change detection is an Angular 18+ mode where the app runs without Zone.js. Instead of being notified on every async task, Angular schedules change detection only when signals change, async pipes emit, or explicit markForCheck() calls happen.

// main.ts bootstrapApplication(AppComponent, { providers: [ provideZonelessChangeDetection() ] });
ZONE-BASED VS ZONELESS
Zone-based (default)
Zone.js patches all async
CD runs on every task
Heavy, simple to reason about
~30KB Zone.js overhead
Zoneless (v18+)
No Zone.js — drop the dep
CD scheduled explicitly
Fast, requires discipline
Smaller bundle, better perf

To go zoneless successfully, you need: signals for state, async pipes for streams, and OnPush-style immutability in any remaining legacy code. The rewards: smaller bundle, faster startup, and crisper stack traces.

→ Key Insight
Zoneless is where Angular is heading strategically. Even if your current app isn't ready, structuring new features around signals + async pipe makes a future migration almost free.
Q46

What are the most effective techniques for optimizing an Angular application?

Performance wins stack on three layers: bundle size, render work, and network.

OPTIMIZATION CHECKLIST
01 · Lazy load routes
loadChildren / loadComponent keeps the initial bundle tiny.
02 · OnPush + signals
Halve or better the number of bindings Angular checks per tick.
03 · trackBy in @for
Stops DOM recreation on list replacements.
04 · Virtual scroll (CDK)
Render only visible rows for long tables/feeds.
05 · Pure pipes over methods
Method calls in templates run every CD. Pure pipes are cached.
06 · NgOptimizedImage
Automatic lazy loading, srcset, priority hints. Free LCP.
07 · runOutsideAngular
Wrap scroll/mousemove handlers to skip CD per event.
08 · @defer blocks
Defer non-critical UI until idle, on-viewport, or on-interaction.

Measure first: ng build --stats-json + source-map-explorer for bundles; the Angular DevTools profiler for CD hot spots; Lighthouse for overall Web Vitals. Don't guess.

→ Real-World Use
Most Angular perf issues come from two places: doing work in the template (method calls, big pipes) and forgetting trackBy on big lists. Fix those before reaching for exotic optimizations.
Part VIII

Advanced & Modern Angular

Topics that separate seniors from the rest — server rendering, hydration, the Ivy compiler, and the testing patterns every team expects from a production-ready developer.

SSR
Hydration
Ivy
Testing
TestBed
Questions 47–50
Q47

What is Angular Universal / SSR, and what benefits does it provide?

Server-Side Rendering (SSR) means rendering the initial HTML of your app on a Node.js server, sending it to the browser, and letting Angular take over on the client. Angular Universal was the name of the old standalone library; since Angular 17 SSR is built into the CLI — ng new --ssr wires it up.

CSR vs SSR — FIRST PAINT
CSR (default)
Browser downloads empty HTML
Downloads & parses JS
App boots, fetches data
Only then does UI appear
Slow FCP, poor SEO
SSR
Server renders full HTML
Browser shows content immediately
JS hydrates in background
Fully interactive once hydrated
Fast FCP, great SEO

Benefits:

BenefitWhy it matters
Faster First Contentful PaintUsers see content before JS has finished loading — huge on slow networks.
SEOCrawlers get a fully rendered page — no more JS-rendering quirks.
Social previewsOpen Graph and Twitter cards work because meta tags are in the served HTML.
AccessibilityScreen readers and no-JS clients see content from the start.

Cost: you now run Node in production, and code that assumes window / document will break on the server. Guard with isPlatformBrowser().

→ Real-World Use
Any marketing site, blog, e-commerce listing, or public-facing page should be SSR. Internal dashboards behind login don't benefit — the user never sees the first paint advantage.
Q48

What is hydration, and what problem does it solve?

Hydration is the process of the client-side Angular app attaching to the server-rendered DOM instead of throwing it away and rebuilding it. Before hydration, SSR apps would briefly show the server HTML, then replace it with a freshly-bootstrapped client tree — causing a visible flicker and wasted work.

NON-HYDRATED VS HYDRATED SSR
Before hydration (pre-v16)
Server HTML shown
Client boots
destroys DOM + rebuilds
→ flicker, wasted CPU
With hydration (v16+)
Server HTML shown
Client boots
attaches to existing nodes
→ no flicker, fast TTI
// main.ts — full hydration bootstrapApplication(AppComponent, { providers: [provideClientHydration()] });

Angular 17 added incremental hydration via @defer blocks: parts of the page can remain as static HTML until the user scrolls to them or interacts — then only those chunks hydrate. That cuts time-to-interactive dramatically on content-heavy pages.

→ Interview Tip
Hydration is table-stakes for modern SSR. If you don't turn it on, you get all the cost of SSR (server) and none of the UX benefit (flicker replaces the speed advantage). Always pair SSR with provideClientHydration().
Q49

What is the Ivy renderer, and how did it change Angular?

Ivy is Angular's compiler and runtime, introduced as default in Angular 9 (2020). It replaced View Engine, the previous pipeline. The design goals were smaller bundles, faster compilation, and better debugging.

VIEW ENGINE → IVY
View Engine (pre-v9)Ivy (v9+)
Compilation modelGlobal, one-shotPer-component, incremental
Tree-shakingLimited — unused code shippedAggressive — only used code ships
Bundle sizeLarger baseline~30–40% smaller for small apps
Build speedSlower, recompiles everythingMuch faster incremental rebuilds
ErrorsCryptic framework stacksHuman-readable locations

Ivy also enabled features that simply weren't possible before: locality (components compile without knowing about their NgModule), standalone components, and eventually fine-grained hydration. Everything modern Angular ships has Ivy underneath.

You don't "use" Ivy directly — it's the compiler. You benefit from it passively every time you ship.

→ Key Insight
Ivy was the re-platforming that unlocked the last five years of Angular improvements. If a feature sounds like "it just couldn't work before" — signals, standalone, incremental hydration — Ivy is why it can work now.
Q50

How do you test Angular components, services, and pipes?

Angular ships a testing harness — TestBed — plus a set of utilities for rendering components in a simulated environment. Paired with Jasmine/Karma (classic) or Jest/Vitest (modern), it covers the whole pyramid.

WHAT TO TEST, HOW
01 · Pipes
Pure functions — instantiate, call transform(), assert output. No TestBed needed.
02 · Services
TestBed to inject. HttpTestingController mocks HTTP.
03 · Components
ComponentFixture renders into a test DOM; query with harnesses or DebugElement.
// component test describe('CounterComponent', () => { let fixture: ComponentFixture<CounterComponent>; beforeEach(() => { TestBed.configureTestingModule({ imports: [CounterComponent] }); fixture = TestBed.createComponent(CounterComponent); }); it('increments on click', () => { fixture.detectChanges(); const btn = fixture.nativeElement.querySelector('button'); btn.click(); fixture.detectChanges(); expect(fixture.nativeElement.textContent).toContain('1'); }); });

Two high-leverage tips: component harnesses (@angular/cdk/testing) give you a stable API instead of brittle querySelectors, and spectator (third-party) cuts the boilerplate of TestBed.configureTestingModule in half.

For end-to-end, Cypress and Playwright have mostly replaced Angular's old Protractor (deprecated in v12).

→ Interview Tip
Say what you test, not just how. "Pipes — pure unit; services — TestBed + HttpTestingController; components — harness-based interaction tests; critical flows — Playwright E2E." That pyramid shows maturity.
Complete

All 50 Questions.
Covered.

From fundamentals to zoneless change detection — everything an Angular engineer needs to walk into an interview confident and leave confident of the answer.

50
Questions
8
Topic Areas
40+
Visual Diagrams
Saurabh Singh
AI Engineer & Builder
linkedin.com/in/iamsausi medium.com/@sausi github.com/sausi-7