Angular developer interviews in 2026 go far beyond “what is a component.” Hiring teams expect signals and zoneless change detection, RxJS operator choice (switchMap vs mergeMap), standalone architecture, and senior judgment on when NgRx is worth the ceremony—not AngularJS trivia from a decade ago.
Below are 45 questions for Angular developer and senior Angular developer loops: framework fundamentals through production architecture, performance, and scenario design. Open each answer after you try the question yourself. For TypeScript interview questions (generics, guards, utility types), see TypeScript interview questions. For React JS interview questions, see React interview questions and answers and React JS interview questions for experienced developers. For dedicated CSS interview questions (flexbox, grid, cascade), see CSS interview questions. For browser-wide HTML, CSS, and general front end depth, see front end developer interview questions. For API and full-stack integration, see full stack developer interview questions.
Interview context and how to prepare
What do Angular developer interviews actually test?
Angular interviews test whether you can ship maintainable enterprise UIs on the modern Angular stack—not only scaffold CLI apps.
| Level | Typical focus |
|---|---|
| Junior | Components, templates, *ngIf/*ngFor, basic services, HTTP |
| Mid | RxJS, reactive forms, routing, lazy loading, unit tests |
| Senior | Change detection, signals, architecture, performance, NgRx trade-offs, migration |
| Staff+ | Monorepo boundaries, design systems, platform APIs, team conventions |
Common formats:
- Live coding (component + service + HTTP)
- Take-home feature with tests
- Architecture whiteboard (dashboard, auth, feature modules)
- Senior deep dives on RxJS and change detection
A strong candidate explains trade-offs with shipped examples—not memorized definition lists.
What extra bar do senior Angular interviews add?
Mid-level loops check if you can build features. Senior loops check if you can own them at scale.
| Area | Mid signal | Senior signal |
|---|---|---|
| State | Service + BehaviorSubject | Signals + NgRx when justified |
| RxJS | subscribe in component |
Operator choice, leak prevention, error strategy |
| CD | Knows OnPush exists | Zoneless, markForCheck, signal consumers |
| Architecture | Feature folders | Standalone boundaries, lazy routes, library APIs |
| Quality | Some unit tests | Testing strategy, a11y, bundle budgets |
Interviewers cite production incidents—memory leaks, slow tables, duplicate HTTP from wrong switchMap—and ask how you prevented recurrence.
What is a typical Angular developer interview loop?
| Round | Duration | Focus |
|---|---|---|
| Recruiter / HM | 30 min | Projects, Angular version shipped, team size |
| TypeScript + Angular fundamentals | 45–60 min | Components, DI, templates, HTTP |
| RxJS + state | 45–60 min | Operators, forms, async pipe, signals |
| Live coding | 45–90 min | List + detail, search, form validation |
| System design | 45–60 min | Large SPA, auth, micro-frontends—senior |
| Behavioral | 30 min | Deadlines, refactors, mentoring |
Partners and product companies often use HackerRank/CoderPad for TypeScript before framework depth.
Read the JD for Angular version (17+ standalone default, 18+ signals maturity, zoneless experiments).
What is a realistic 4–6 week prep plan?
| Week | Focus | Output |
|---|---|---|
| 1 | TypeScript — generics, unions, strict mode | 15 small typing exercises |
| 2 | Components, standalone, templates, pipes | Rebuild one screen standalone |
| 3 | RxJS — switchMap, mergeMap, exhaustMap, async pipe |
Typeahead + button-submit labs |
| 4 | Signals + change detection + OnPush | Migrate one component to signals |
| 5 | Routing, guards, lazy load, reactive forms | Mini app with auth guard |
| 6 | Testing + scenarios — Vitest/Jest, architecture STAR | One whiteboard + three stories |
Pair with front end interviews for CSS, a11y, and Core Web Vitals vocabulary.
Components, standalone architecture, and templates
What is an Angular component?
An Angular component is the basic building block of the UI. It combines:
- A template that defines what the user sees
- A class that holds state, behavior, and dependencies
@Componentmetadata such asselector,templateUrl,styleUrl, andimports- Inputs and outputs for communication with parent components
@Component({
selector: 'app-user-card',
standalone: true,
imports: [CommonModule],
template: `<h2>{{ user().name }}</h2>`,
})
export class UserCardComponent {
user = input.required<User>();
}In modern Angular, components are usually standalone, so their template dependencies are listed directly in imports instead of being declared through an NgModule.
A good interview answer should also mention component design:
- Presentational components receive data through inputs and emit events
- Smart/container components coordinate services, routing, API calls, and state
- HTTP logic should normally live in services, not directly inside reusable UI components
A strong answer is:
“A component controls one part of the UI. Its metadata tells Angular how to render it, the class holds state and behavior, and standalone imports make its template dependencies explicit.”
What are standalone components and why are they default now?
Standalone components do not need to be declared in an NgModule. They import their own dependencies directly:
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [UserCardComponent, RouterLink],
templateUrl: './dashboard.html',
})
export class DashboardComponent {}| Older NgModule style | Standalone style |
|---|---|
Components declared in declarations |
Components import dependencies directly |
| Shared modules often re-export many things | Dependencies are visible in the component |
Lazy loading commonly used loadChildren |
Routes can use loadComponent |
| More boilerplate | Smaller, clearer dependency graph |
Standalone components are preferred because they make Angular apps:
- Easier to lazy load
- Easier to tree-shake
- Easier to migrate gradually
- Easier for new developers to understand
- Less dependent on large shared modules
Legacy NgModule-based apps still exist, so interviewers may expect you to understand both styles.
A strong answer is:
“Standalone components remove unnecessary NgModule boilerplate. Each component imports what it uses, which makes dependencies clearer and supports simpler lazy loading and migration.”
input(), output(), and @Input/@Output — what changed?
Angular now supports signal-based component inputs and outputs.
Modern style:
count = input(0);
save = output<Item>();Older decorator style:
@Input() count = 0;
@Output() save = new EventEmitter<Item>();The newer input() API gives you a signal-like value that works well with computed() and template reactivity.
Example:
name = input.required<string>();
displayName = computed(() => this.name().trim().toUpperCase());model() is used when a component needs two-way binding:
checked = model(false);This automatically supports a matching change output, such as checkedChange.
Important interview points:
@Input()and@Output()are still common in existing appsinput()works better with signals and modern Angular patternsoutput()replaces many directEventEmitterdeclarationsmodel()is useful for custom form-like components- Outputs should notify parents about UI events, not contain complex business logic
A strong answer is:
“The older decorators still work, but modern Angular favors
input(),output(), andmodel()because they fit better with signal-based reactivity and make component APIs easier to reason about.”
Which lifecycle hooks still matter in 2026?
The most important lifecycle hooks are still useful, but signals reduce the need for some older patterns.
| Hook / API | When to use |
|---|---|
ngOnInit |
One-time initialization after Angular creates the component |
ngOnChanges |
Reacting to legacy @Input() changes |
ngAfterViewInit |
Accessing view children after the view is initialized |
ngOnDestroy |
Cleanup for timers, subscriptions, and external resources |
afterNextRender() |
Run DOM-related work once after Angular renders |
afterEveryRender() |
Run DOM-related work after every render |
Modern Angular patterns:
- Use
computed()for derived state instead of manually recalculating in lifecycle hooks - Use
effect()carefully for side effects - Use
takeUntilDestroyed()instead of manual unsubscribe subjects - Use
afterNextRender()for DOM work like charts, focus, or non-Angular libraries
Avoid doing heavy work in the constructor. The constructor should mainly inject dependencies.
A strong answer is:
“Lifecycle hooks still matter, but I avoid using them for derived state. I use signals and
computed()for state,ngOnInitfor initialization,ngOnDestroyfor cleanup, and render callbacks for DOM-dependent work.”
Why are pipes preferred over template functions?
Pipes are usually better than calling functions directly from templates because Angular can optimize pure pipes.
<!-- Prefer -->
{{ price | currency:'USD' }}
<!-- Avoid for expensive work -->
{{ formatPrice(price) }}A pure pipe runs again only when its input value changes. A template function can be called repeatedly during change detection, which becomes expensive in large lists or frequently updated screens.
This is especially risky when the function:
- Performs calculations
- Filters or sorts arrays
- Creates new objects or arrays
- Calls another service method
- Runs inside
@for/*ngFor
The async pipe is also important because it:
- Subscribes to an observable
- Updates the view when a new value arrives
- Unsubscribes automatically when the component is destroyed
- Marks the component for checking when needed
Use a template function only when it is simple, cheap, and does not create new references.
A strong answer is:
“Pipes are preferred because Angular can optimize pure pipes, while template functions may run repeatedly during change detection. For expensive formatting, filtering, or observable values, pipes are safer and cleaner.”
Structural vs attribute directives?
Angular has two major directive categories.
| Type | Examples | What it does |
|---|---|---|
| Structural directive | *ngIf, *ngFor, *ngSwitch |
Adds, removes, or repeats DOM views |
| Attribute directive | ngClass, ngStyle, custom appHighlight |
Changes behavior, style, or properties of an existing element |
Example structural directive:
<div *ngIf="isLoggedIn">Welcome</div>Example attribute directive:
<div [ngClass]="{ active: isSelected }">User</div>Modern Angular also has built-in control flow:
@if (isLoggedIn) {
<p>Welcome</p>
}
@for (user of users; track user.id) {
<app-user-card [user]="user" />
}@if, @for, and @switch are now preferred in new code because they are clearer and optimized by the Angular compiler.
@defer is another important modern feature. It lets Angular delay loading part of the UI until a trigger such as viewport, idle time, interaction, or hover.
A strong answer is:
“Structural directives change the DOM structure, while attribute directives change the behavior or appearance of an existing element. In modern Angular, I prefer the new
@ifand@forcontrol flow for new templates.”
Change detection, Zone.js, and signals
How does Angular change detection work?
Angular change detection keeps the DOM in sync with component state.
In older zone-based Angular apps, the common flow is:
- A browser event, timer, promise, or HTTP request completes
- Zone.js notifies Angular
- Angular checks component bindings
- Angular updates the DOM where values changed
In newer zoneless Angular apps, Angular does not rely on Zone.js to know that something changed. Instead, change detection is triggered by clear notifications such as:
- Signal updates
- Template events
asyncpipe emissionsmarkForCheck()- Framework APIs that notify Angular
Change detection strategy also matters:
| Strategy | Behavior |
|---|---|
| Default / eager | Angular checks more broadly when activity happens |
| OnPush | Angular checks a component when inputs change, events happen, signals update, or it is marked for check |
Interviewers usually want to know both the old and modern model.
For performance-heavy screens, such as tables, dashboards, and charts, you should reduce unnecessary checks by using:
- OnPush
- Signals
trackwith@for- Pure pipes
- Pagination or virtual scrolling
- Avoiding expensive template functions
A strong answer is:
“Change detection compares component state with template bindings and updates the DOM. Older apps often rely on Zone.js to trigger checks, while modern Angular uses signals and explicit notifications, especially with OnPush and zoneless apps.”
When do you use ChangeDetectionStrategy.OnPush?
OnPush tells Angular to skip unnecessary checks and only re-check a component when Angular has a clear reason.
In modern Angular, OnPush is recommended and may already be the default in new projects. In older projects, you often enable it manually:
@Component({
selector: 'app-user-list',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './user-list.html',
})
export class UserListComponent {}OnPush works well when the view is driven by:
- Inputs
- Signals
- Observables with
asyncpipe - Immutable object updates
- Local template events
Common mistake:
// Mutates the same object reference
this.user.name = 'New name';Better:
// Creates a new reference
this.user = { ...this.user, name: 'New name' };With signals, you update state through signal APIs:
user.update(value => ({ ...value, name: 'New name' }));Use OnPush especially for:
- Reusable UI components
- Large component trees
- Lists and tables
- Dashboard widgets
- Components that receive data from parent/state services
Be careful when a component depends on mutable singleton state that changes without signals, observables, or markForCheck().
A strong answer is:
“I use OnPush to reduce unnecessary checks and encourage predictable data flow. It works best with immutable inputs, signals, and async pipe. The main mistake is mutating objects without changing the reference or notifying Angular.”
What is zoneless Angular?
Zoneless Angular means Angular does not depend on Zone.js to schedule change detection.
In a zone-based app, Angular often runs change detection after async browser activity because Zone.js patches APIs like events, timers, and promises.
In a zoneless app, Angular updates the UI when there is an explicit signal that something changed, such as:
- A signal value changes
- A template event runs
- An observable bound with
asyncemits ChangeDetectorRef.markForCheck()is called- Angular framework APIs schedule an update
Typical bootstrap configuration:
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection(),
],
});Why teams like zoneless:
- Smaller runtime dependency
- Fewer unexpected change detection cycles
- Better performance control
- Better fit with signals
- Easier debugging of “what caused this render?”
Migration concerns:
- Older third-party libraries may assume Zone.js exists
- Code that updates state outside Angular must notify Angular
- Existing manual
NgZone.run()usage needs review - OnPush-compatible components migrate more easily
A strong answer is:
“Zoneless Angular removes the dependency on Zone.js for scheduling change detection. It works best when state changes are visible through signals, async pipe, events, or explicit
markForCheck()calls.”
signal(), computed(), and effect() — explain each.
Angular signals are reactive values that tell Angular where state is used and when it changes.
const count = signal(0);
const double = computed(() => count() * 2);
effect(() => {
console.log('count changed:', count());
});| Primitive | Meaning | Best use |
|---|---|---|
signal() |
Writable reactive value | Local UI state |
computed() |
Derived read-only value | View-model calculations |
effect() |
Side effect when dependencies change | Logging, local storage, external APIs |
Example:
firstName = signal('Deepak');
lastName = signal('Prasad');
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);Use computed() when a value can be derived from other state. Do not store duplicate state manually if Angular can derive it.
Use effect() carefully. It is not the best place for normal business logic or state propagation. It is better for side effects such as:
- Logging
- Syncing to local storage
- Calling non-Angular APIs
- Updating a chart library
Important interview detail: signals are read by calling them as functions.
count(); // read signal value
A strong answer is:
“
signal()stores reactive state,computed()derives values from other signals, andeffect()runs side effects. In most UI code, I prefercomputed()for derived state and useeffect()only when I need to interact with something outside normal template rendering.”
Signals vs RxJS — when do you use each?
Signals and RxJS solve related but different problems.
| Signals | RxJS Observables |
|---|---|
| Hold a current value | Represent values over time |
| Synchronous | Can be async, cancellable, and composable |
| Great for UI state | Great for HTTP, websockets, events, streams |
Read with signal() |
Subscribe manually or use async pipe |
| Works naturally with templates | Strong operators for async workflows |
Use signals for:
- Component state
- Derived view-models
- Form UI state
- Toggle/filter/sort state
- Values shown directly in templates
Use RxJS for:
- HTTP request pipelines
- Websocket streams
- Debounced search
- Retry/cancel/error flows
- Combining multiple async sources
- Complex event streams
Bridge APIs:
user = toSignal(this.userService.user$);
user$ = toObservable(this.user);Modern Angular also has resource APIs for async signal-based data loading, such as resource(), rxResource(), and httpResource().
A practical pattern:
- Keep service-level async flows in RxJS when operators are useful
- Convert observables to signals near the component/template boundary
- Use signals for derived UI state
- Avoid replacing every observable just because signals exist
A strong answer is:
“RxJS is still excellent for async streams and operators. Signals are better for current UI state and derived template values. I usually let streams feed the system and use signals to feed the template.”
markForCheck() vs detach() on ChangeDetectorRef?
Both APIs are used for manual change detection control, but they solve different problems.
| API | What it does | When to use |
|---|---|---|
markForCheck() |
Marks an OnPush view as dirty so Angular checks it in the next cycle | External async updates, manual integrations |
detach() |
Removes a view from automatic change detection | Advanced high-frequency rendering control |
reattach() |
Adds the view back to automatic checking | Resume normal Angular updates |
detectChanges() |
Immediately checks the current view and children | Manual checking after detach |
Example use of markForCheck():
constructor(private cdr: ChangeDetectorRef) {}
loadData() {
someExternalApi((value) => {
this.value = value;
this.cdr.markForCheck();
});
}Use markForCheck() when Angular would not otherwise know that something changed.
Use detach() only for advanced cases, such as:
- Very high-frequency chart updates
- Large read-only lists updated in batches
- Manual rendering loops
- Performance-sensitive dashboards
Be careful: if you detach a component and forget to call detectChanges() or reattach(), the UI can become stale.
With signals and async pipe, you usually need less manual ChangeDetectorRef code because Angular receives clearer change notifications.
A strong answer is:
“
markForCheck()asks Angular to check an OnPush component later.detach()removes a component from automatic checks, so I must calldetectChanges()orreattach()manually. I usedetach()only for advanced performance cases.”
RxJS and async patterns
What is an Observable and how does Angular use RxJS?
An Observable represents a stream of values over time. A subscriber reacts when the observable emits a value, throws an error, or completes.
Angular uses RxJS in many places:
HttpClientrequests- Router events and route params
- Reactive forms
valueChangesandstatusChanges - Websocket/event streams
- Interop with signals through
toSignal()andtoObservable()
Important interview difference:
| Promise | Observable |
|---|---|
| Usually one value | Can emit zero, one, or many values |
| Starts immediately once created | Often lazy until subscribed |
Basic chaining with then() |
Rich operators like map, switchMap, retry |
| Cancellation is not built in directly | Subscription can be cancelled |
HTTP observables from HttpClient are usually cold. That means each subscription can trigger a new HTTP request unless the stream is shared.
const users$ = this.http.get<User[]>('/api/users');
// Two subscriptions may create two HTTP requests
users$.subscribe();
users$.subscribe();For one-time template reads, use async pipe. For shared/cached streams, use a service-level strategy such as shareReplay, cache invalidation, or a signal/resource-based store.
A strong answer is:
“An Observable is a lazy stream that can emit multiple values over time. Angular uses RxJS for HTTP, routing, forms, and async UI flows. Compared with Promises, observables are cancellable, composable, and better for streams.”
switchMap vs mergeMap vs concatMap vs exhaustMap?
These are higher-order mapping operators. They map each source value to an inner observable, but they handle overlapping inner subscriptions differently.
| Operator | Behavior | Best use |
|---|---|---|
switchMap |
Cancels previous inner observable when a new value arrives | Typeahead search, route-param reload |
mergeMap |
Runs inner observables in parallel | Independent parallel requests |
concatMap |
Queues inner observables one after another | Ordered writes, sequential saves |
exhaustMap |
Ignores new values while current inner observable is active | Login/submit button double-click prevention |
Example: latest search term wins.
searchTerm$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.api.search(term))
);Use the wrong operator and you can create real bugs:
mergeMapfor search can show stale resultsswitchMapfor writes can cancel a save requestconcatMapfor long tasks can create a backlogexhaustMapfor search can ignore the latest user input
A practical rule:
- Read latest data:
switchMap - Write in order:
concatMap - Run independent work:
mergeMap - Ignore duplicate clicks:
exhaustMap
A strong answer is:
“I choose the operator based on concurrency.
switchMapcancels old work,mergeMapruns parallel work,concatMappreserves order, andexhaustMapprevents duplicate submissions while one request is active.”
How do you avoid RxJS memory leaks?
RxJS memory leaks usually happen when a component subscribes to a long-lived observable and does not unsubscribe when the component is destroyed.
Preferred patterns:
- Use the
asyncpipe in templates - Use
takeUntilDestroyed()for component subscriptions - Use
takeUntil(destroy$)in older Angular code - Avoid nested manual
subscribe()calls - Prefer services/facades for complex stream orchestration
Template approach:
@if (user$ | async; as user) {
<p>{{ user.name }}</p>
}Component approach:
this.form.valueChanges
.pipe(takeUntilDestroyed())
.subscribe(value => {
this.filter.set(value);
});Be careful with long-lived streams such as:
valueChanges- Router events
- Websocket streams
- DOM event streams
- Store selectors
- Subjects in services
HTTP requests usually complete after one response, but you should still avoid messy manual subscriptions when async pipe or toSignal() can express the same flow more cleanly.
Senior-level point: shareReplay() can prevent duplicate HTTP requests, but it can also accidentally keep data alive forever if you do not think about refCount, cache reset, TTL, or invalidation.
A strong answer is:
“I avoid leaks by preferring
asyncpipe, usingtakeUntilDestroyed()for manual subscriptions, and keeping long-lived stream orchestration in services. I also treatshareReplaycarefully because caching without invalidation can become a hidden memory and stale-data issue.”
Why prefer async pipe over manual subscribe?
The async pipe is preferred because it handles subscription lifecycle automatically.
Benefits:
- Subscribes to the observable
- Renders the latest emitted value
- Marks the view for checking when a new value arrives
- Unsubscribes automatically when the component is destroyed
- Unsubscribes from the old stream if the observable reference changes
- Works well with
OnPush
@if (user$ | async; as user) {
<p>{{ user.name }}</p>
}Manual subscription adds boilerplate:
ngOnInit() {
this.sub = this.userService.user$.subscribe(user => {
this.user = user;
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}Use manual subscribe only when you are doing side effects that do not belong directly in the template, such as:
- Updating a non-Angular chart library
- Calling browser APIs
- Logging analytics
- Coordinating imperative workflows
Even then, use takeUntilDestroyed() or another teardown pattern.
A strong answer is:
“I prefer
asyncpipe because it keeps the component class clean, works with OnPush, and handles subscribe/unsubscribe automatically. I manually subscribe only for side effects or orchestration that cannot be expressed in the template.”
How do you handle errors in HTTP streams?
HTTP error handling should happen at the right layer.
Common patterns:
- Use
catchError()for fallback values or mapped errors - Use
retry()only for transient failures - Use an
HttpInterceptorfor global concerns like auth errors - Show user-friendly errors through a toast, banner, or error signal
- Use
finalize()when loading state must reset on success or failure
users$ = this.http.get<User[]>('/api/users').pipe(
retry({ count: 2, delay: 500 }),
catchError(error => {
this.error.set('Unable to load users');
return of([]);
})
);Do not blindly retry everything. Retrying a failed GET may be fine for temporary network issues. Retrying a POST can create duplicate writes unless the backend is idempotent.
Also remember: when an observable errors, that stream ends unless catchError() replaces it with another observable.
Good separation:
| Layer | Responsibility |
|---|---|
| Interceptor | Auth token, 401 handling, correlation ID, global logging |
| Service | API-specific mapping, retry rules, fallback |
| Component | User-facing message and view state |
A strong answer is:
“I handle expected request errors close to the service, use interceptors for global HTTP concerns, retry only safe transient failures, and expose a clear loading/error state to the UI.”
Subject variants — when use each?
A Subject is both an observable and an observer. It can multicast values to multiple subscribers.
| Type | Behavior | Typical use |
|---|---|---|
Subject |
No initial value, no replay | Fire-and-forget events |
BehaviorSubject |
Requires initial value and emits latest value to new subscribers | Current state in older RxJS services |
ReplaySubject |
Replays N previous values to new subscribers | Event/history replay |
AsyncSubject |
Emits only the last value when completed | Rare; final result on completion |
Example older service pattern:
private readonly userSubject = new BehaviorSubject<User | null>(null);
readonly user$ = this.userSubject.asObservable();Modern Angular alternative for simple UI state:
private readonly user = signal<User | null>(null);
readonly currentUser = this.user.asReadonly();Use subjects carefully. They are useful, but overusing them can create hidden event buses that are hard to trace.
Prefer:
- Signals for simple current UI state
- RxJS streams for async workflows
- NgRx/SignalStore for larger shared state
- Explicit service methods instead of random public subjects
Avoid exposing writable subjects publicly.
// Avoid
userSubject = new BehaviorSubject(null);
// Prefer
private readonly userSubject = new BehaviorSubject<User | null>(null);
readonly user$ = this.userSubject.asObservable();A strong answer is:
“I use
Subjectfor events,BehaviorSubjectfor current-value state in RxJS services,ReplaySubjectwhen new subscribers need history, andAsyncSubjectrarely. In modern Angular, I often use signals instead of BehaviorSubject for simple UI state.”
Forms, routing, and HTTP
Reactive forms vs template-driven forms?
Angular supports both reactive forms and template-driven forms.
| Area | Reactive forms | Template-driven forms |
|---|---|---|
| Form model | Explicit in TypeScript | Mostly created from template directives |
| Data flow | Synchronous and predictable | More template-driven |
| Testing | Easier to unit test | More dependent on template behavior |
| Dynamic forms | Strong choice | Awkward for complex cases |
| Validation | Validator functions | Validator directives |
| Best for | Enterprise/complex forms | Simple forms |
Use reactive forms for:
- Login/registration forms with validation
- Dynamic fields
- Cross-field validation
- Async validation
- Multi-step forms
- Enterprise forms with testing needs
Use template-driven forms for:
- Small contact forms
- Simple newsletter signup
- Basic admin inputs
- Forms with minimal validation
Example async validator scenario:
- User types username
- Form waits briefly using debounce
- API checks availability
switchMapcancels stale checks- Validation result updates the control
Interviewers may also ask about:
- Custom validators
- Cross-field validators
- Async validators
ControlValueAccessor- Typed forms
- Form arrays
A strong answer is:
“I use reactive forms for serious application forms because the model is explicit, testable, and easier to scale. Template-driven forms are fine for simple forms with limited validation.”
Lazy loading and route configuration in standalone apps?
Lazy loading reduces the initial JavaScript bundle by loading route code only when needed.
In standalone Angular apps, routes are usually configured as plain route arrays.
export const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
},
{
path: 'item/:id',
loadComponent: () =>
import('./item/item.component').then(m => m.ItemComponent),
},
];Use:
| API | Best for |
|---|---|
loadComponent |
Lazy loading one standalone component |
loadChildren |
Lazy loading a feature route tree |
canMatch |
Prevent matching/loading routes based on conditions |
canActivate |
Protect route activation |
resolve |
Fetch data before route activation |
Modern Angular also supports functional guards and resolvers:
export const authGuard: CanActivateFn = () => {
return inject(AuthStore).isLoggedIn();
};Important senior discussion:
- Use
canMatchwhen you want to stop a route from matching or loading - Use
canActivatewhen the route can match but needs access control - Use lazy loading for large feature areas, admin panels, reports, and rarely used routes
- Use preloading strategies when you want faster navigation after initial load
- Avoid putting all routes into one large eager bundle
Resolvers are still useful, but for many modern apps, component-level loading with signals/resources gives more flexible loading and error states.
A strong answer is:
“In standalone Angular, I lazy load either a single component with
loadComponentor a feature route tree withloadChildren. I use guards for access control, resolvers when data must exist before activation, and preloading when I want better navigation after startup.”
What do HTTP interceptors do?
HTTP interceptors sit in the HttpClient pipeline. They can inspect or transform outgoing requests and incoming responses.
Common uses:
- Add auth tokens
- Add correlation/request IDs
- Normalize base URLs
- Log request timing
- Map API errors
- Handle 401/403 responses
- Retry selected requests
Modern standalone-friendly style uses functional interceptors:
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthStore).token();
const authReq = token
? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
: req;
return next(authReq);
};Registration example:
provideHttpClient(
withInterceptors([authInterceptor])
);Keep interceptors thin. Heavy business logic should stay in services.
Good interceptor logic:
- Add token
- Attach headers
- Log timing
- Redirect on auth failure
- Convert technical errors to application errors
Bad interceptor logic:
- Component-specific UI decisions
- Complex business workflows
- Silent retry of unsafe writes
- Large conditional logic for many unrelated APIs
A strong answer is:
“An interceptor wraps the HTTP pipeline. I use it for cross-cutting concerns such as auth headers, logging, correlation IDs, and global error handling, while keeping feature-specific logic inside services.”
Dependency injection and architecture
How does Angular dependency injection work?
Angular dependency injection lets classes ask for dependencies instead of creating them manually.
Example service:
@Injectable({ providedIn: 'root' })
export class UserService {}Example injection:
private readonly userService = inject(UserService);Angular resolves a token from an injector hierarchy. Common provider scopes:
| Provider location | Result |
|---|---|
providedIn: 'root' |
App-wide singleton |
| Application config providers | App-level provider |
| Route providers | Provider scoped to route tree |
Component providers |
New instance for that component subtree |
inject() is commonly used in:
- Components
- Services
- Functional guards
- Functional resolvers
- Functional interceptors
- Factory providers
Constructor injection is still valid:
constructor(private userService: UserService) {}Senior-level point: component-level providers can intentionally create isolated instances.
Example:
@Component({
providers: [WizardStateService],
})
export class CheckoutWizardComponent {}Each wizard subtree gets its own state service instead of sharing one global singleton.
A strong answer is:
“Angular DI resolves tokens through a hierarchy of injectors.
providedIn: 'root'gives an app-wide singleton, while route or component providers create narrower scopes. I choose provider scope based on the lifetime of the state or service.”
@Host, @Self, @SkipSelf, @Optional?
DI modifiers control how Angular searches for a dependency in the injector hierarchy.
| Modifier | Meaning |
|---|---|
@Self() / { self: true } |
Look only in the current injector |
@SkipSelf() / { skipSelf: true } |
Skip current injector and start from parent |
@Host() / { host: true } |
Stop searching at the host boundary |
@Optional() / { optional: true } |
Return null instead of throwing if not found |
Modern inject() style:
const parentConfig = inject(ConfigService, {
skipSelf: true,
optional: true,
});When they are useful:
Self— a directive requires a local provider on the same elementSkipSelf— a child wants the parent instance, not its ownOptional— a reusable component can work with or without a providerHost— a library component should not accidentally resolve too far up the tree
Example scenario:
A reusable form-control component may optionally read a parent form field provider. If the provider is missing, the component should still render instead of crashing.
const parent = inject(ControlContainer, { optional: true });These modifiers are more common in framework/library code than everyday feature components.
A strong answer is:
“DI modifiers restrict Angular’s provider lookup. I use them when building reusable components, directives, or libraries where provider scope must be explicit and accidental parent resolution would create bugs.”
When would you choose NgRx vs signals in services?
Use the simplest state management that fits the problem.
| Choose signals + service | Choose NgRx / SignalStore |
|---|---|
| Feature-local UI state | Large shared state |
| Simple derived values | Complex state transitions |
| Small team or small feature | Multiple teams touching same state |
| No strict event history needed | Actions/events improve traceability |
| Fast iteration | Enterprise conventions and DevTools matter |
Signals in a service work well for:
- Filters
- Selected tab
- Modal state
- Local feature preferences
- Current loaded entity
- Simple derived view models
NgRx is useful when you need:
- Clear action history
- Reducer/effect discipline
- DevTools
- Complex async workflows
- Shared state across many routes
- Large-team consistency
SignalStore is a pragmatic middle ground. It gives a structured store pattern while using Angular signals naturally.
Avoid cargo-culting NgRx. A three-screen internal tool may not need actions, reducers, effects, and selectors everywhere.
But also avoid under-engineering. If many components mutate shared state in different ways, a formal store can prevent bugs.
A strong answer is:
“I start with signals in a service for simple feature state. I move to NgRx or SignalStore when state becomes shared, complex, event-driven, or maintained by multiple teams. I do not use NgRx just because the project is Angular.”
Smart vs presentational components?
Smart and presentational components separate data orchestration from reusable UI rendering.
Presentational components:
- Receive data through inputs
- Emit events through outputs
- Do not call HTTP APIs directly
- Do not know about global routing/store details
- Are easy to reuse and test
user = input.required<User>();
selected = output<string>();Smart/container components:
- Load data
- Inject services or stores
- Handle route params
- Build view models
- Pass data down to presentational components
- React to output events
Typical flow:
- Container reads route params
- Container loads data through service/store
- Container passes data to UI component
- UI component emits user action
- Container calls service/store
This pattern prevents reusable UI components from becoming tied to one backend or route.
Folder example:
user/
containers/
user-page.component.ts
ui/
user-card.component.ts
user-table.component.ts
data-access/
user-api.service.tsDo not take the pattern too far. Tiny features do not need unnecessary folders and indirection.
A strong answer is:
“Presentational components render data and emit events. Smart components fetch data, coordinate services, and handle state. This keeps UI components reusable and prevents API logic from spreading across the template layer.”
Nx / monorepo boundaries for Angular?
Nx helps manage large Angular workspaces with multiple apps and libraries.
Common monorepo structure:
apps/
admin/
customer-portal/
libs/
shared/ui/
shared/util/
users/feature/
users/data-access/
users/ui/Important concepts:
| Concept | Meaning |
|---|---|
| App | Deployable application |
| Library | Reusable code unit |
| Feature lib | Route/page-level feature logic |
| UI lib | Reusable presentational components |
| Data-access lib | API clients, stores, facades |
| Util lib | Pure helpers and shared types |
| Boundary rules | Prevent invalid imports |
| Affected builds | Test/build only what changed |
Why boundaries matter:
- Prevent circular dependency problems
- Stop UI libraries from importing data-access internals
- Keep features independent
- Make refactoring safer
- Allow teams to own separate areas
- Speed up CI with affected commands
Example boundary idea:
| Library type | Can depend on |
|---|---|
feature |
ui, data-access, util |
ui |
util only |
data-access |
util only |
util |
No feature-specific library |
Senior interviewers may ask about:
@nx/enforce-module-boundaries- Tag-based dependency constraints
- Circular dependencies
- Shared UI libraries
- Publishable vs buildable libraries
- Affected builds and parallel CI
- Ownership between teams
A good answer should focus on architecture, not only tooling.
A strong answer is:
“Nx is useful when an Angular codebase grows into many apps and libraries. I use libraries to separate feature, UI, data-access, and utility code, then enforce boundaries so teams do not create circular dependencies or import across layers incorrectly.”
Performance, testing, and security
How do you optimize Angular app performance?
A good performance answer should start with measurement, not random optimization.
First measure with:
- Angular DevTools
- Chrome Performance panel
- Lighthouse / Core Web Vitals
- Network tab and bundle analysis
- Real user monitoring if available
Then optimize based on the bottleneck.
| Problem | Useful technique |
|---|---|
| Too much change detection | OnPush, signals, pure pipes |
| Large lists | @for with track, CDK virtual scroll |
| Heavy initial bundle | Lazy routes, @defer, code splitting |
| Slow images | NgOptimizedImage, width/height, priority hints |
| Expensive template work | Avoid template functions, precompute with computed() |
| Repeated HTTP calls | Cache carefully, share streams, use state services |
| Slow public page load | SSR/hydration or SSG where useful |
| Unused services/code | Tree-shakable providers and dead-code removal |
Modern Angular performance tools include:
ChangeDetectionStrategy.OnPush- Signals and
computed() - Built-in control flow with
@for @deferfor heavy widgets- Lazy route loading
- SSR/hydration for faster first render
- Virtual scrolling for large lists
Example performance bug:
@for (user of users(); track user.id) {
<app-user-card [user]="user" />
}Without a stable track expression, Angular may recreate more DOM than needed when the list changes.
Senior-level point: performance is not only frontend code. Sometimes the real fix is backend pagination, smaller API payloads, CDN caching, or image optimization.
A strong answer is:
“I first measure the bottleneck, then optimize the right layer. In Angular, I usually look at change detection, list rendering, bundle size, lazy loading, image loading, and duplicate async work before making changes.”
SSR and hydration — interview basics?
SSR means Angular renders the initial HTML on the server. The browser receives meaningful HTML before the full client-side JavaScript finishes loading.
Hydration means Angular reuses the server-rendered DOM and attaches client-side behavior to it instead of destroying and recreating the page.
| Concept | Meaning |
|---|---|
| CSR | Browser renders the app after JavaScript loads |
| SSR | Server sends already-rendered HTML |
| SSG | HTML generated at build time |
| Hydration | Client Angular attaches behavior to server HTML |
| Incremental hydration | Hydrate deferred parts of the page only when needed |
SSR/hydration can help with:
- Faster first content
- Better perceived performance
- SEO for public pages
- Lower client-side rendering cost at startup
But SSR is not always required. For an internal authenticated dashboard, SSR may be less valuable than lazy loading, caching, and API performance.
Common hydration pitfalls:
- Direct DOM access too early
- Using
window,document, orlocalStorageduring server render - Random IDs or dates causing server/client mismatch
- Third-party scripts modifying DOM before hydration
- Different templates rendered on server and client
Safer patterns:
- Use
afterNextRender()for DOM-dependent browser work - Guard browser-only APIs when SSR is enabled
- Keep templates deterministic
- Defer heavy client-only widgets
A strong answer is:
“SSR gives the browser HTML earlier, and hydration attaches Angular behavior without a full client re-render. I use it mainly for public or performance-sensitive pages, but I watch for DOM mismatch and browser-only API issues.”
What testing strategy do interviewers expect?
Interviewers expect a practical testing strategy, not just “we write unit tests.”
A healthy Angular testing approach includes:
| Test type | What to test |
|---|---|
| Unit tests | Services, validators, pipes, reducers, pure functions |
| Component tests | Inputs, outputs, template behavior |
| HTTP tests | API service behavior without real network calls |
| Integration tests | Feature flow with multiple components/services |
| E2E tests | Critical user journeys in browser |
Modern Angular HTTP tests should use provideHttpClientTesting().
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
provideHttpClientTesting(),
UserApiService,
],
});
});Component input test example:
it('shows users', () => {
const fixture = TestBed.createComponent(UserListComponent);
fixture.componentRef.setInput('users', [
{ id: '1', name: 'Ada' },
]);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Ada');
});Good testing habits:
- Test behavior, not implementation details
- Mock API services at clear boundaries
- Avoid brittle DOM selectors
- Test custom validators separately
- Use Angular Material/CDK harnesses where available
- Keep E2E tests focused on important flows
Senior-level answer: do not test every small DOM detail with E2E. Use many fast unit/component tests and fewer high-value browser tests.
A strong answer is:
“I use unit tests for logic, component tests for UI behavior, HTTP tests for API services, and a small number of E2E tests for critical journeys. I avoid brittle tests that only verify implementation details.”
Angular security topics for interviews?
Angular has built-in security protections, but developers still need to follow safe patterns.
Important Angular security topics:
| Topic | What to say |
|---|---|
| XSS | Avoid rendering untrusted HTML |
| Sanitization | Angular sanitizes many bindings automatically |
DomSanitizer |
Use bypass APIs only when content is truly trusted |
| CSRF/XSRF | Cookie-based auth needs server/client token support |
| Auth tokens | Avoid unsafe storage for high-risk applications |
| Route guards | UX/access control, not backend security |
| Interceptors | Add auth headers and handle auth errors |
| CSP / Trusted Types | Useful defense-in-depth for strict security apps |
Example risk:
<div [innerHTML]="userProvidedHtml"></div>Angular sanitizes HTML bindings, but you should still avoid displaying untrusted rich HTML unless the product really requires it.
Dangerous pattern:
this.sanitizer.bypassSecurityTrustHtml(userInput);That tells Angular to trust the value. Do this only for content from a trusted source, never blindly for user input.
Route guards are not enough security. A user can still call backend APIs directly. The backend must enforce authorization.
Good interview answer should mention both frontend and backend:
- Frontend protects UI and reduces attack surface
- Backend enforces real authorization
- Security-sensitive decisions must not depend only on Angular route guards
A strong answer is:
“Angular helps prevent XSS through sanitization, but I still avoid untrusted HTML, use
DomSanitizervery carefully, handle CSRF/auth correctly, and never treat route guards as the only security layer.”
Scenario-based and migration questions
Scenario: Implement search typeahead with HTTP.
A strong typeahead design avoids sending a request for every keystroke and avoids showing stale results.
Typical flow:
- Create a
FormControl - Listen to
valueChanges - Add
debounceTime(300) - Add
distinctUntilChanged() - Ignore very short search terms if needed
- Use
switchMap()to call HTTP - Handle loading and errors
- Render with
asyncpipe or convert to a signal
Example:
results$ = this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
filter(term => term.length >= 2),
switchMap(term =>
this.api.search(term).pipe(
catchError(() => of([]))
)
)
);Why switchMap()?
Because the latest search should win. If the user types a, then an, then ang, older requests should not update the screen after the latest request.
Do not use mergeMap() for normal typeahead. It allows parallel requests, so a slower old request may return later and show stale results.
Good UI details:
- Show loading state
- Show empty state
- Show error state
- Disable search for very short terms
- Cache results if the same query repeats often
- Use OnPush and avoid template functions in result lists
A strong answer is:
“For typeahead, I debounce the input, ignore duplicate terms, and use
switchMapso stale HTTP requests are cancelled. I also handle loading, empty, and error states so the UI behaves predictably.”
Scenario: Architect a large analytics dashboard?
For a large analytics dashboard, split the design into routing, state, rendering, API, and resilience.
Key architecture choices:
| Area | Approach |
|---|---|
| Routing | Lazy load dashboard sections |
| Shared filters | Signal service, SignalStore, or NgRx |
| Widgets | Presentational chart/table components |
| Data access | Dedicated API/data-access services |
| Rendering | OnPush, signals, @defer |
| Tables | Pagination or CDK virtual scroll |
| Error handling | Per-widget error and retry state |
| Security | Guard routes and enforce backend authorization |
A good dashboard should not fail as one giant page. Each widget should be able to show:
- Loading state
- Empty state
- Error state
- Retry action
- Last updated timestamp where useful
Performance plan:
- Lazy load heavy routes
- Defer heavy charts below the fold
- Use API pagination for large tables
- Use
@forwith stabletrack - Avoid recalculating chart data in templates
- Cache shared reference data
- Reduce repeated HTTP calls between widgets
State management decision:
- Simple dashboard: signal service or SignalStore
- Many widgets and teams: NgRx/SignalStore with clearer conventions
- Complex filter sync with URL: route query params + store/facade
Senior-level angle: frontend optimization may not be enough. For reporting dashboards, backend aggregation, API shape, caching, and database queries often matter more than Angular code.
A strong answer is:
“I would lazy load dashboard sections, keep shared filters in a clear store/facade, make widgets presentational, use OnPush and
@defer, and design every widget with independent loading, error, and retry states.”
Scenario: Migrate NgModule app to standalone?
A good migration answer should be incremental, not a big-bang rewrite.
Suggested plan:
- Check the current Angular version and upgrade safely with
ng update - Run tests and fix version-related breaking changes first
- Convert leaf components to standalone
- Add required
importsdirectly in components - Convert route-level components and lazy routes
- Replace module-based routing with route arrays
- Move toward
bootstrapApplication() - Remove empty or unnecessary NgModules
- Keep NgModule interop where migration risk is high
- Measure bundle size and test each feature slice
Migration schematic example:
ng generate @angular/core:standaloneGood migration order:
leaf UI components
→ feature components
→ route configuration
→ bootstrap
→ shared module cleanupAvoid these mistakes:
- Converting the whole application in one PR
- Removing shared modules before imports are clear
- Mixing version upgrades and architecture migration without tests
- Rewriting working features only for style
- Ignoring lazy route boundaries
Signals migration can happen separately. Do not force signal inputs into every component during the first standalone migration unless that component is already being changed.
A strong answer is:
“I migrate NgModule apps to standalone gradually. I start with leaf components, then feature routes, then bootstrap, while keeping tests green and avoiding a risky big-bang rewrite.”
Scenario: Migrate BehaviorSubject UI state to signals?
A good migration starts by identifying what kind of state the BehaviorSubject represents.
Keep RxJS when the state is really a stream:
- Websocket events
- Debounced user input
- Retry/cancel HTTP pipelines
- Complex async composition
- Multiple event sources needing operators
Use signals when the state is current UI/application state:
- Selected tab
- Current filters
- Expanded rows
- Loaded user shown in template
- Derived view model
Before:
private readonly filterSubject = new BehaviorSubject('');
readonly filter$ = this.filterSubject.asObservable();
setFilter(value: string) {
this.filterSubject.next(value);
}After:
private readonly filter = signal('');
readonly currentFilter = this.filter.asReadonly();
setFilter(value: string) {
this.filter.set(value);
}Derived state:
readonly filteredUsers = computed(() =>
this.users().filter(user =>
user.name.includes(this.currentFilter())
)
);Migration steps:
- Find
BehaviorSubjectstate used mainly by templates - Replace with private writable signal
- Expose read-only signal
- Move derived values to
computed() - Keep HTTP as observable when RxJS operators are useful
- Use
toSignal()at the component boundary if needed - Remove manual subscriptions from components
- Add tests for old and new behavior
Do not migrate blindly. A BehaviorSubject used inside a complex RxJS pipeline may be better left as RxJS.
A strong answer is:
“I migrate BehaviorSubject state to signals when it represents current UI state. I keep RxJS for async streams and operator-heavy workflows, then bridge to signals near the template.”
AngularJS (1.x) vs modern Angular — still asked?
Yes, it is still asked in some legacy enterprise interviews.
AngularJS and modern Angular are different frameworks.
| AngularJS 1.x | Modern Angular |
|---|---|
| JavaScript-first | TypeScript-first |
Controllers and $scope |
Components and services |
| Digest cycle | Change detection with zones/signals |
ng-repeat |
*ngFor / @for |
ng-if |
*ngIf / @if |
| Older architecture | Standalone components, signals, RxJS |
In interviews, clarify what the job description means:
- AngularJS usually means version 1.x legacy apps
- Angular usually means Angular 2+ modern TypeScript framework
If the company still has AngularJS, they may ask about migration. Good migration talking points:
- Do not rewrite everything at once
- Identify stable feature boundaries
- Upgrade build/test pipeline first
- Migrate route by route or feature by feature
- Keep old and new systems working during transition
- Replace
$scopepatterns with component/service architecture
A strong answer is:
“AngularJS is the older 1.x framework based on controllers and
$scope. Modern Angular is a TypeScript component framework. If I see both in a company, I clarify whether the role is maintenance, migration, or new Angular development.”
How do you approach accessibility in Angular apps?
Accessibility should be part of normal Angular development, not a final polish step.
Important practices:
- Use semantic HTML first
- Use real buttons and links where possible
- Add labels for form controls
- Ensure keyboard navigation works
- Manage focus in dialogs and route changes
- Use ARIA only when native HTML is not enough
- Provide visible focus styles
- Maintain color contrast
- Announce important dynamic updates
- Test with keyboard, screen reader smoke tests, axe, and Lighthouse
Angular/CDK tools:
| Tool | Use |
|---|---|
| CDK a11y | Accessibility helpers |
| Focus trap | Keep focus inside dialogs/modals |
| Live announcer | Announce dynamic updates to screen readers |
| List/key managers | Keyboard navigation for custom widgets |
Common Angular-specific examples:
- After opening a modal, move focus into it
- After closing a modal, return focus to the opener
- For custom dropdowns, support arrow keys and Escape
- For async validation errors, make the message readable to screen readers
- For route changes, update page title and focus the main content
Avoid building custom components when native controls work. A native <button> is usually better than a clickable <div>.
A strong answer is:
“I start with semantic HTML, then add keyboard support, focus management, labels, contrast, and screen-reader announcements where needed. For complex widgets, I use Angular CDK a11y utilities instead of inventing everything manually.”
What changed with built-in control flow (@if, @for)?
Modern Angular supports built-in template control flow.
Older style:
<div *ngIf="user">
{{ user.name }}
</div>
<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>Modern style:
@if (user(); as u) {
<p>{{ u.name }}</p>
} @else {
<p>Guest</p>
}
@for (item of items(); track item.id) {
<app-row [item]="item" />
} @empty {
<p>No items found</p>
}Benefits:
- Clearer syntax
- Better type narrowing
- Less reliance on structural directive imports
- More explicit tracking in loops
- Better fit with signals
@for requires thinking about identity. Use a stable tracking expression:
@for (user of users(); track user.id) {
<app-user-card [user]="user" />
}Avoid tracking by index when the list can be reordered, inserted into, or deleted from. Tracking by stable ID prevents unnecessary DOM churn and identity bugs.
@defer is related but different. It delays loading/rendering a block until a trigger happens.
@defer (on viewport) {
<app-heavy-chart />
} @placeholder {
<p>Chart loading...</p>
}A strong answer is:
“Built-in control flow gives Angular templates clearer
@if,@for, and@switchsyntax. I especially pay attention to stabletrackvalues in@forbecause they directly affect DOM reuse and performance.”
What TypeScript depth do senior Angular interviews expect?
Senior Angular interviews expect more than basic TypeScript syntax.
Important TypeScript areas:
| Topic | Angular use |
|---|---|
| Generics | Observable<T>, typed services, typed forms |
| Union types | Loading/error/success UI states |
| Discriminated unions | Safer state machines |
readonly |
Immutable inputs and state |
unknown vs any |
Safer API/error handling |
| Utility types | Partial<T>, Pick<T>, Record<K,V> |
satisfies |
Validate object shape without losing inference |
| Strict null checks | Safer templates and API models |
| Type guards | Narrow unknown API values |
| Typed forms | Safer reactive form controls |
Example UI state model:
type UserState =
| { status: 'loading' }
| { status: 'loaded'; users: User[] }
| { status: 'error'; message: string };This is better than several unrelated booleans:
isLoading = false;
hasError = false;
users = [];
errorMessage = '';Because impossible states become harder to represent.
Senior candidates should also understand API typing:
- DTO from backend may differ from UI model
- Nullable fields should be modeled honestly
- Avoid
anyfor HTTP responses - Use
unknownat unsafe boundaries and validate/narrow it
A strong answer is:
“Senior Angular developers should use TypeScript to model real application states, not just annotate variables. I use generics, strict null checks, discriminated unions, and typed forms to catch bugs before runtime.”
Behavioral: tell me about a difficult Angular migration or performance fix.
Use the STAR format.
| Part | What to cover |
|---|---|
| Situation | What was broken or risky? |
| Task | What were you responsible for? |
| Action | What did you change? |
| Result | What improved measurably? |
Example structure:
Situation:
The dashboard was slow because a large table and several charts rendered at once.
Task:
Improve load time without rewriting the full application.
Action:
- Profiled with Angular DevTools and Chrome Performance
- Added OnPush to stable presentational components
- Replaced template functions with computed values/pipes
- Added stable
trackvalues to list rendering - Deferred heavy charts below the fold
- Moved repeated API calls into a shared data service
- Worked with backend team on pagination
Result:
- Initial render improved
- Duplicate requests reduced
- Table interactions became smoother
- Team adopted a checklist for new dashboard widgets
For migration stories, mention:
- Incremental rollout
- Tests before each slice
- Feature flags if needed
- Rollback plan
- Bundle/performance comparison
- Developer communication
Avoid vague answers like “I optimized the app.” Interviewers want to hear what you measured, what you changed, and what result you achieved.
A strong answer is:
“I would explain the production problem, how I measured it, the Angular-specific fixes I applied, and the measurable result. A strong story shows technical skill, risk control, and collaboration.”
What is the resource() / httpResource() API (modern async)?
Angular’s resource APIs model async data as reactive state.
httpResource() is a signal-friendly wrapper around HttpClient. It gives you request state such as value, loading, and error in a way that works naturally with signals and templates.
Typical idea:
userResource = httpResource<User>(() =>
`/api/users/${this.userId()}`
);Template usage:
@if (userResource.isLoading()) {
<p>Loading...</p>
} @else if (userResource.error()) {
<p>Unable to load user</p>
} @else {
<app-user-card [user]="userResource.value()" />
}Why it matters:
- Reduces manual
subscribe()code - Reduces manual loading/error flags
- Reacts to signal changes
- Works well with
computed()and templates - Still uses Angular
HttpClientunderneath
Good use cases:
- Loading data from route params
- Search/detail pages
- Read-heavy screens
- Template-friendly async state
Where RxJS may still be better:
- Complex operator pipelines
- Websocket streams
- Debounced typeahead
- Multi-step workflows
- Writes with careful retry/cancel rules
- Existing service APIs already built around observables
Interview-safe answer: know the API exists, but do not claim it replaces all RxJS.
A strong answer is:
“
resource()andhttpResource()help model async data as signal-based state with loading, value, and error handling. I would use them for read-heavy UI data, while keeping RxJS for complex streams and operator-heavy workflows.”
Final-week checklist for Angular developer interviews?
Use the final week to revise common interview patterns, not every Angular API.
Must revise:
- Component basics, standalone imports, and smart/dumb split
-
input(),output(),model(), and legacy@Input/@Output - Lifecycle hooks,
afterNextRender(), and cleanup - OnPush, signals, and change detection
- Zone.js vs zoneless Angular
-
signal(),computed(), andeffect() - Signals vs RxJS
-
switchMap,mergeMap,concatMap,exhaustMap -
asyncpipe andtakeUntilDestroyed() - Reactive forms, validators, and
ControlValueAccessor - Lazy routes, guards, and resolvers
- HTTP interceptors and error handling
- DI scopes and modifiers
- NgRx vs signal services vs SignalStore
- Performance: OnPush,
@for track,@defer, lazy loading - Testing strategy and HTTP testing
- Security: XSS, sanitization, CSRF, route guards
- Accessibility basics
- Migration from NgModule to standalone
- Migration from BehaviorSubject state to signals
Scenario drills:
- Build a search typeahead
- Fix stale search results
- Prevent double form submit
- Design a large dashboard
- Debug a memory leak
- Improve a slow list/table
- Add auth token with interceptor
- Create a custom form control
- Migrate one feature to standalone
- Explain a production performance fix using STAR
Good closing line for an interview:
“I try to choose Angular patterns deliberately. I use standalone components, OnPush, signals, RxJS, and stores based on the problem, not by habit. I can work in modern Angular, but I can also migrate and maintain older Angular code safely.”
Pattern cheat sheet (quick reference)
| Need | Reach for |
|---|---|
| Typeahead HTTP | debounceTime + distinctUntilChanged + switchMap |
| Prevent double submit | exhaustMap |
| Ordered writes | concatMap |
| Parallel independent calls | mergeMap |
| UI state | signal() + computed() |
| Complex async streams | RxJS operators |
| Template observable | async pipe |
| Manual component subscription | takeUntilDestroyed() |
| Non-trivial forms | Reactive forms |
| Custom form component | ControlValueAccessor |
| Lazy page | loadComponent or loadChildren |
| Auth header | HttpInterceptorFn |
| Large list | @for with stable track, virtual scroll |
| Heavy widget | @defer |
| Public SEO page | SSR/hydration or SSG |
| Feature-local state | Signal service |
| Enterprise shared state | SignalStore or NgRx |
| Library boundaries | Nx tags and module-boundary rules |
| Accessibility widgets | Angular CDK a11y |
| Security-sensitive HTML | Avoid or sanitize carefully |
References
Official Angular documentation
On-site prep
- Front end developer interviews
- Full stack developer interviews
- Node.js developer interviews
- Java interview questions — part 1
- Git interview questions
- SQL technical interview questions
- Interview Questions category
Summary
Angular interviews—especially at the senior level—focus on signals, RxJS discipline, standalone architecture, and change detection more than CLI trivia. Practice defending operator choices, migrating legacy NgModule apps, and keeping templates fast with OnPush, track, and @defer. Use this guide as a self-test: answer aloud, then compare your structure to each collapsed response. Pair with front end and full stack prep when the role spans the whole delivery stack.

