🎯 Free Interview Preparation

Angular Interview Master Guide

Complete Angular interview preparation guide for candidates and interviewers.

Angular Interview Master Guide (2026)

Complete Angular interview preparation guide for candidates and interviewers.

Question #1: What is Angular?

Level: Junior | Category: Angular Fundamentals | Time: 5 mins

Expected Answer

Angular is a TypeScript-based frontend framework developed by Google for building Single Page Applications (SPA).

Practical Example

Applications like Admin Dashboards, CRM Systems, ERP Applications, Banking Portals and Enterprise Platforms are commonly built using Angular.

Follow-Up Questions

  • Why Angular over React?
  • What is TypeScript?
  • What is a SPA?
Interviewer Note: 💡 Strong candidates explain Angular using real projects.

Question #2: What is a Component?

Level: Junior | Category: Components | Time: 5 mins

Expected Answer

Components are the fundamental building blocks of Angular applications. Each component controls a part of the UI.

Practical Example

  • Navbar Component
  • Sidebar Component
  • Product Card Component
  • Login Component

Code Example


@Component({
 selector: 'app-user',
 templateUrl: 'user.html'
})
export class UserComponent {
}

Follow-Up Questions

  • What is a standalone component?
  • Difference between Component and Directive?
  • What are Inputs and Outputs?

Question #3: Explain Data Binding in Angular

Level: Junior | Category: Templates | Time: 5 mins

Expected Answer

Data Binding is the mechanism used to synchronize data between the component and the view.

Data Binding Types

Interpolation
{{ name }}
Property Binding
[disabled]="isLoading"
Event Binding
(click)="save()"
Two-Way Binding
[(ngModel)]
Interviewer Note: 💡 Candidate should explain when each binding type is used.

Question #4: What are Services in Angular?

Level: Junior | Category: Dependency Injection | Time: 5 mins

Expected Answer

Services are used to share business logic and data between components.

Practical Example


@Injectable({
 providedIn: 'root'
})
export class UserService {
}

Real World Usage

  • API Calls
  • Authentication
  • User Profile Data
  • Shopping Cart Management

Question #5: Explain Angular Change Detection

Level: Mid-Level | Category: Change Detection | Time: 8 mins

Expected Answer

Change Detection is Angular's mechanism for synchronizing application state with the DOM.

Whenever data changes, Angular checks components and updates the UI accordingly.

Key Concepts

  • Default Strategy
  • OnPush Strategy
  • Zone.js
  • markForCheck()
  • detectChanges()

Practical Example

Large dashboards containing hundreds of components often use OnPush to improve rendering performance.

Interviewer Note: 💡 Strong candidates explain why OnPush improves performance.

Question #6: What are RxJS Observables?

Level: Mid-Level | Category: RxJS | Time: 8 mins

Expected Answer

Observables represent asynchronous streams of data that can emit multiple values over time.

Real World Usage

  • HTTP Requests
  • WebSockets
  • Search Inputs
  • Notifications
  • Real-Time Dashboards

Code Example


this.userService.getUsers()
.subscribe((users) => {
  this.users = users;
});

Follow-Up Questions

  • Observable vs Promise?
  • Cold vs Hot Observables?
  • Why use async pipe?

Question #7: Explain switchMap, mergeMap and concatMap

Level: Mid-Level | Category: RxJS | Time: 8 mins

switchMap

Cancels previous requests.

Best for Search

mergeMap

Runs requests in parallel.

Best for Bulk Operations

concatMap

Executes requests sequentially.

Best for Ordered Operations
Interviewer Note: 💡 🚨 This question quickly identifies candidates with real RxJS experience.

Question #8: How do Memory Leaks occur in Angular?

Level: Mid-Level | Category: Performance | Time: 8 mins

Expected Answer

Memory leaks commonly occur when subscriptions remain active after components are destroyed.

Common Causes

  • Forgotten subscriptions
  • Event listeners
  • Timers
  • WebSocket connections

Best Practices

  • takeUntil()
  • async pipe
  • DestroyRef
  • takeUntilDestroyed()

Question #9: Explain Lazy Loading

Level: Mid-Level | Category: Lazy Loading | Time: 8 mins

Expected Answer

Lazy Loading loads modules only when required, reducing initial bundle size.

Benefits

  • Faster Initial Load
  • Smaller Bundle Size
  • Improved User Experience

Practical Example

Admin module should not load until a user navigates to administrative pages.

Question #10: What is Angular SSR?

Level: Senior | Category: SSR | Time: 12 mins

Expected Answer

Server-Side Rendering renders HTML on the server before sending it to the browser.

Benefits

  • SEO Improvement
  • Faster First Contentful Paint
  • Better Social Sharing
  • Improved Performance

Real Project Example

Public-facing websites like DevStacker, eCommerce stores, blogs and news portals benefit significantly from SSR.

Question #11: Explain Angular Hydration

Level: Senior | Category: SSR | Time: 12 mins

Expected Answer

Hydration attaches Angular functionality to HTML already rendered by the server, avoiding complete re-rendering.

Why It Matters

  • Improved Performance
  • Reduced Flickering
  • Better User Experience
  • Faster Interactivity
Interviewer Note: 💡 Strong candidates understand SSR and Hydration together.

Question #12: Signals vs RxJS

Level: Senior | Category: Signals | Time: 12 mins

Signals

  • Synchronous
  • Simple State
  • Component State
  • Easy Learning Curve

RxJS

  • Asynchronous Streams
  • Complex Workflows
  • HTTP
  • WebSockets

Interviewer Expectation

Candidate should explain when to use Signals, when to use RxJS and when to combine both.

Production Scenario #13: Dashboard Becomes Slow After 30 Minutes

Level: Senior | Category: Advanced Topics | Time: 12 mins

Scenario

Users report that the dashboard becomes unusable after remaining open for 30 minutes.

Expected Investigation

  • Memory Profiling
  • Subscription Leaks
  • Change Detection Loops
  • Network Analysis
  • Chrome Performance Tab
Interviewer Note: 💡 🚨 Candidates who jump directly to code changes without investigation often lack debugging experience.

Question #14: How do you secure an Angular application?

Level: Senior | Category: Advanced Topics | Time: 12 mins

Expected Topics

  • XSS Protection
  • CSP Headers
  • Route Guards
  • Authentication
  • Authorization
  • Token Management

Architecture Challenge #15: Design a CRM Application

Level: Senior | Category: Architecture | Time: 12 mins

Requirements

  • 100,000 Users
  • Role Based Access
  • Real-Time Notifications
  • Offline Support
  • Multi-Tenant Architecture

Expected Discussion

  • Folder Structure
  • State Management
  • Caching
  • API Layer
  • Deployment Strategy

Architect Question #1: Monolith vs Micro Frontend Architecture

Level: Architect | Category: Architecture | Time: 15 mins

Scenario

Your organization has 15 frontend teams working on a large CRM platform. Would you choose a monolithic Angular application or Micro Frontends?

Expected Discussion

  • Team Independence
  • Deployment Strategy
  • Shared Libraries
  • Performance Trade-offs
  • Operational Complexity
Interviewer Note: 💡 Strong architects discuss trade-offs instead of claiming one approach is always superior.

Architect Question #2: How would you build a Design System?

Level: Architect | Category: Architecture | Time: 15 mins

Expected Topics

  • Component Library
  • Storybook
  • Accessibility
  • Theming
  • Versioning
  • Documentation

Real World Challenge

Multiple teams want custom components that violate standards. How do you maintain consistency while allowing flexibility?

Architect Question #3: When should you use Nx Monorepo?

Level: Architect | Category: Architecture | Time: 15 mins

Advantages

  • Shared Libraries
  • Code Reuse
  • Consistent Standards
  • Centralized CI/CD

Challenges

  • Repository Size
  • Build Complexity
  • Learning Curve
  • Governance

Architecture Challenge: Design an Enterprise Angular Platform

Level: Architect | Category: Forms | Time: 15 mins

Requirements

  • 50 Development Teams
  • 200 Applications
  • Shared Component Library
  • Shared Authentication
  • Global Theming
  • Centralized Logging

Expected Architecture Topics

  • Monorepo vs Polyrepo
  • Shared UI Libraries
  • Module Federation
  • CI/CD Pipelines
  • Governance

Architect Question #5: How would you reduce frontend infrastructure costs?

Level: Architect | Category: Advanced Topics | Time: 15 mins

Expected Discussion

  • CDN Optimization
  • Bundle Size Reduction
  • Caching Strategy
  • SSR Cost Analysis
  • Cloud Resource Optimization

Leadership Scenario: Two Senior Engineers Disagree on Architecture

Level: Architect | Category: Signals | Time: 15 mins

Scenario

One engineer wants NgRx. Another wants Signals-based state management.

What Interviewers Look For

  • Decision Framework
  • Evidence-Based Discussions
  • Team Alignment
  • Technical Leadership

Junior Challenge: Build a Todo Application

Level: Coding Challenge | Category: Advanced Topics | Time: 20 mins

Requirements

  • Add Todo
  • Delete Todo
  • Mark Complete
  • Display Todo Count

Skills Evaluated

  • Components
  • Data Binding
  • Events
  • Basic State Management
Interviewer Note: 💡 Look for clean code rather than perfect UI.

Junior Challenge: Build a Registration Form

Level: Coding Challenge | Category: Forms | Time: 20 mins

Requirements

  • Name
  • Email
  • Password
  • Validation
  • Submit Button

Mid-Level Challenge: Build a Reusable Search Component

Level: Coding Challenge | Category: RxJS | Time: 20 mins

Requirements

  • API Integration
  • Debounce
  • Error Handling
  • Loading State
  • Cancel Previous Requests

Expected Concepts

  • RxJS
  • switchMap
  • Reactive Forms
  • Observables

Mid-Level Challenge: Build a Reusable Table Component

Level: Coding Challenge | Category: Components | Time: 20 mins

Features

  • Sorting
  • Filtering
  • Pagination
  • Loading State
  • Reusable Columns

Senior Challenge: Optimize a Slow Dashboard

Level: Coding Challenge | Category: Signals | Time: 20 mins

Scenario

Dashboard contains 100 widgets and becomes slow after loading.

Expected Discussion

  • OnPush
  • Signals
  • trackBy
  • Virtual Scrolling
  • Lazy Loading

Senior Challenge: Refactor Legacy Angular Code

Level: Coding Challenge | Category: Advanced Topics | Time: 20 mins

Interview Goal

Evaluate architecture thinking, maintainability and code quality.

Debugging Exercise: Users Report Random Page Freezes

Level: Coding Challenge | Category: Advanced Topics | Time: 20 mins

Scenario

Production users report occasional browser freezes.

Expected Investigation Process

  1. Reproduce Issue
  2. Review Logs
  3. Analyze Memory Usage
  4. Check Network Requests
  5. Profile CPU Usage

Angular Trap #1: What causes ExpressionChangedAfterItHasBeenCheckedError?

Level: Tricky Question | Category: Advanced Topics | Time: 10 mins

Expected Answer

Angular detected that a value changed after change detection completed.

Common Scenario

Updating component state inside ngAfterViewInit().

Wrong Answer

"Just use setTimeout()".

Strong Candidate Answer

Explain the root cause, lifecycle timing, and when ChangeDetectorRef should be used.

Angular Trap #2: Why can an OnPush component still re-render?

Level: Tricky Question | Category: Signals | Time: 10 mins

Expected Discussion

  • Input Reference Changes
  • Events Inside Component
  • Observable Emissions
  • Signal Updates
  • markForCheck()
Interviewer Note: 💡 Many developers incorrectly believe OnPush completely disables change detection.

Angular Trap #3: Why is ViewChild undefined?

Level: Tricky Question | Category: Advanced Topics | Time: 10 mins

Common Causes

  • Accessing too early
  • *ngIf not rendered yet
  • Wrong lifecycle hook
  • Static flag misunderstanding

Expected Knowledge

Difference between static:true and static:false.

Angular Trap #4: Why does Async Pipe help prevent memory leaks?

Level: Tricky Question | Category: Templates | Time: 10 mins

Expected Answer

Async Pipe automatically subscribes and unsubscribes when the component is destroyed.

Interviewer Note: 💡 🚨 Strong candidates also explain situations where async pipe is not sufficient.

Angular Trap #5: When should Signals NOT be used?

Level: Tricky Question | Category: Signals | Time: 10 mins

Expected Answer

  • Complex Async Workflows
  • WebSocket Streams
  • Event Processing Pipelines
  • Complex RxJS Operators

Strong Candidate Signal

Candidate understands that Signals and RxJS complement each other.

Production Scenario: UI Not Updating But Data Changes

Level: Tricky Question | Category: Signals | Time: 10 mins

Expected Investigation

  • OnPush Strategy
  • Mutating Objects
  • Signal Updates
  • Observable Subscription
  • ChangeDetectorRef

Angular Trap #7: Why doesn't OnPush detect this change?

Level: Tricky Question | Category: Advanced Topics | Time: 10 mins

Code Example

user.name = 'John';

Expected Answer

Object reference didn't change. Only a property changed.

Angular Trap #8: Why does switchMap cancel previous requests?

Level: Tricky Question | Category: Advanced Topics | Time: 10 mins

Expected Discussion

  • Search Inputs
  • Request Cancellation
  • Latest Value Wins
  • Race Condition Prevention

Production Incident #1: Dashboard Performance Degrades Over Time

Level: Production Incident | Category: Forms | Time: 15 mins

Scenario

Users report that the application becomes slower after keeping it open for several hours.

Possible Root Causes

  • Memory Leaks
  • Unreleased Subscriptions
  • WebSocket Accumulation
  • Excessive Change Detection
  • DOM Growth

Expected Investigation

  1. Reproduce Issue
  2. Open Chrome Performance Tab
  3. Record Memory Usage
  4. Analyze Heap Snapshots
  5. Review Subscription Cleanup

Production Incident #2: Page Load Time Increased From 2 Seconds To 12 Seconds

Level: Production Incident | Category: Advanced Topics | Time: 15 mins

Scenario

Users report application startup has become extremely slow.

Investigation Areas

  • Network Waterfall
  • Bundle Size
  • API Response Time
  • Third Party Scripts
  • SSR Rendering Time
Interviewer Note: 💡 Strong candidates investigate before optimizing.

Production Incident #3: Browser Memory Continuously Increases

Level: Production Incident | Category: Signals | Time: 15 mins

Common Causes

  • Forgotten Subscriptions
  • Timers
  • Event Listeners
  • Large Cached Objects
  • Detached DOM Nodes

Modern Angular Solutions

  • DestroyRef
  • takeUntilDestroyed()
  • Async Pipe
  • Signal Cleanup

Authentication Incident: Users Randomly Lose Sessions

Level: Production Incident | Category: Advanced Topics | Time: 15 mins

Possible Causes

  • Expired JWT
  • Refresh Token Failure
  • Clock Synchronization Issues
  • Cookie Configuration
  • Load Balancer Problems

Real-Time Incident: Server Crashes During Peak Usage

Level: Production Incident | Category: Advanced Topics | Time: 15 mins

Scenario

A dashboard receives thousands of real-time updates per minute.

Expected Discussion

  • Throttling
  • Debouncing
  • Buffering
  • Aggregation
  • Backpressure Strategies

SSR Incident: SSR Works Locally But Fails In Production

Level: Production Incident | Category: SSR | Time: 15 mins

Common Causes

  • window Access
  • document Access
  • Third Party Libraries
  • Node Version Mismatch
  • Build Configuration

Angular 18+ Scenario: Hydration Errors Appear In Production

Level: Production Incident | Category: SSR | Time: 15 mins

Potential Causes

  • Different Server Data
  • Random Values
  • Date Rendering Differences
  • Client-Side Mutations

Performance Challenge: Rendering 100,000 Records

Level: Production Incident | Category: Advanced Topics | Time: 15 mins

Expected Solutions

  • Virtual Scrolling
  • Pagination
  • Infinite Scroll
  • OnPush
  • trackBy

Question #45: What are Standalone Components in Angular, and how do they differ from NgModule-based components?

Level: Junior | Category: Standalone Components | Time: 5 mins

Expected Answer

Standalone Components are self-contained components that do not require declaration in an @NgModule. Instead, they specify their dependencies directly using the imports array in their decorator.

  • Simplification: Removes NgModules boilerplate, making the codebase easier to understand and maintain.
  • Explicit Dependencies: Every standalone component explicitly declares what components, directives, and pipes it imports.
  • Easier Tree-shaking: Unused features are easier for the compiler to detect and exclude from production bundles.

Code Example


import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

@Component({
  selector: 'app-standalone-card',
  standalone: true,
  imports: [CommonModule, RouterModule],
  template: `
    <div class="card">
      <h3>Standalone Card</h3>
      <a routerLink="/details">View Details</a>
    </div>
  `
})
export class StandaloneCardComponent {}

Best Practices

  • Migrate all legacy components to standalone components using Angular CLI migration tools.
  • Keep components focused and modular, avoiding importing massive modules if only a single directive is needed.
  • Bootstrap applications using bootstrapApplication() instead of platformBrowserDynamic().bootstrapModule().
Interviewer Note: 💡 Look for candidate's understanding of why Standalone components simplify the component tree and improve bundle sizes.

Question #46: Explain the difference between @Component and @Directive. When should you use a Directive?

Level: Junior | Category: Directives | Time: 5 mins

Expected Answer

A @Component is actually a specialized type of @Directive that has a template (HTML) and styles (CSS). A @Directive is used to add behavior to existing HTML elements or components without rendering a separate template.

  • Components: Used to define new UI blocks (e.g., custom buttons, forms, full pages) with associated markup.
  • Directives: Used to encapsulate re-usable behaviors or styles (e.g., hover effects, input masking, visibility checks).

Code Example


import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {
  @Input() color = 'yellow';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.el.nativeElement.style.backgroundColor = this.color;
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.el.nativeElement.style.backgroundColor = null;
  }
}

Best Practices

  • Use Attribute Directives for style and behavioral additions.
  • Use Structural Directives (with a prepended asterisk like *ngIf) to add or remove DOM elements dynamically.
  • Write standalone directives so they can be easily shared and imported.
Interviewer Note: 💡 Strong candidates explain directives using examples of code reuse across different UI components.

Question #47: What is Dependency Injection (DI) in Angular, and what is the difference between providedIn: 'root' and component-level providers?

Level: Junior | Category: Dependency Injection | Time: 5 mins

Expected Answer

Dependency Injection is a design pattern where a class requests dependencies from external sources rather than creating them itself. Angular has a hierarchical DI framework.

  • providedIn: 'root': Declares the service globally as a singleton. It is tree-shakeable, meaning if no component imports the service, it won't be included in the bundle.
  • Component-level providers: Restricts the service scope to that component and its children. A new instance of the service is created for every instance of the component, which is not tree-shakeable.

Code Example


// Singleton Service
@Injectable({
  providedIn: 'root'
})
export class GlobalConfigService {
  config = { api: '/api' };
}

// Component-scoped Service
@Component({
  selector: 'app-scoped-widget',
  standalone: true,
  providers: [LocalWidgetService],
  template: `<p>Widget Active</p>`
})
export class ScopedWidgetComponent {
  constructor(private widgetService: LocalWidgetService) {}
}
Interviewer Note: 💡 Candidates should know that providedIn: 'root' is preferred for global services due to bundle optimization advantages.

Question #48: What are Angular Pipes, and what is the difference between pure and impure pipes?

Level: Junior | Category: Pipes | Time: 5 mins

Expected Answer

Pipes are template modifiers used to format or transform values directly in HTML interpolation.

  • Pure Pipes (Default): Executed only when Angular detects a change in the input value reference (primitive values or reference change for objects). They are highly performant because Angular caches the result.
  • Impure Pipes: Executed on every change detection cycle, regardless of whether the inputs changed. This can cause massive performance issues if they perform expensive operations.

Code Example


import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncate',
  pure: true,
  standalone: true
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit = 20): string {
    if (!value) return '';
    return value.length > limit ? value.substring(0, limit) + '...' : value;
  }
}

Common Mistakes

Do not create impure pipes that perform array filtering or sorting inside templates, as they execute hundreds of times per second and will lag the user interface. Use computed signals instead.

Interviewer Note: 💡 Check if the candidate knows when to avoid pipes (e.g. heavy calculations, network requests) and uses them only for lightweight formatting.

Question #49: Explain Template-driven vs. Reactive Forms. When should you use which?

Level: Junior | Category: Forms | Time: 5 mins

Expected Answer

Angular supports two forms architectures:

  • Template-driven Forms: Rely on directives in the HTML template (like ngModel) to build the data model. They are asynchronous, simple, and ideal for basic login forms or filters.
  • Reactive Forms: Rely on programmatic form groups and controls defined in TypeScript. They are synchronous, highly testable, offer robust validation APIs, and easily support dynamic inputs.

Code Example


// Reactive Form Example
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-login-form',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <input formControlName="email" type="email" />
      <button type="submit" [disabled]="loginForm.invalid">Submit</button>
    </form>
  `
})
export class LoginFormComponent {
  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email])
  });

  onSubmit() {
    console.log(this.loginForm.value);
  }
}
Interviewer Note: 💡 Modern enterprise applications almost exclusively use Reactive Forms because of their robustness and testability.

Question #50: What is the purpose of Lifecycle Hooks in Angular? Detail ngOnInit, ngOnChanges, and ngOnDestroy.

Level: Junior | Category: Lifecycle Hooks | Time: 6 mins

Expected Answer

Lifecycle Hooks allow components and directives to execute logic at key stages of their existence (initialization, data binding updates, DOM checks, and destruction).

  • ngOnChanges: Fires before ngOnInit and whenever input properties (@Input) change. Receives a SimpleChanges object containing old and new values.
  • ngOnInit: Executed once after data-bound inputs are initialized. Used for initial data fetching and service setup.
  • ngOnDestroy: Executed right before the component is destroyed. Crucial for cleaning up event listeners, intervals, and unsubscribing from RxJS streams to prevent memory leaks.

Best Practices

  • Keep constructor code clean. Use it only for DI. Place initial loading logic in ngOnInit.
  • Always clean up active subscriptions inside ngOnDestroy.
  • For modern Angular code, prefer using DestroyRef or takeUntilDestroyed() instead of implementing ngOnDestroy manually.
Interviewer Note: 💡 Ask the candidate: 'Does ngOnInit run when component properties change later?' (Answer: No, only once).

Question #51: What is the difference between @ViewChild and @ContentChild?

Level: Junior | Category: Components | Time: 5 mins

Expected Answer

Both decorators query references to child elements or directives from the DOM, but they look in different parts of the component hierarchy:

  • @ViewChild: Queries references within the component's own HTML template. For example, grabbing a reference to a custom video player component inside the parent's HTML.
  • @ContentChild: Queries references projected into the component via content projection (inside <ng-content> tags).

Code Example


@Component({
  selector: 'app-parent',
  standalone: true,
  template: `
    <!-- ViewChild target -->
    <input #myInput type="text" />
    
    <!-- ContentChild target projected -->
    <app-custom-card>
      <h2 #cardTitle>Projected Title</h2>
    </app-custom-card>
  `
})
export class ParentComponent {
  @ViewChild('myInput') inputEl!: ElementRef;
}

@Component({
  selector: 'app-custom-card',
  standalone: true,
  template: `
    <div class="card">
      <ng-content></ng-content>
    </div>
  `
})
export class CustomCardComponent {
  @ContentChild('cardTitle') titleEl!: ElementRef;
}
Interviewer Note: 💡 ViewChild references are usually resolved in ngAfterViewInit, whereas ContentChild references are resolved in ngAfterContentInit.

Question #52: How does Angular prevent Cross-Site Scripting (XSS) attacks out of the box?

Level: Junior | Category: Security | Time: 5 mins

Expected Answer

Angular treats all template values as untrusted by default. When values are dynamically bound to properties (e.g. [innerHTML], [src]), Angular automatically sanitizes them to strip malicious script injections.

  • Sanitization: Removes unsafe elements (like <script>, JavaScript links) before rendering.
  • DomSanitizer: A built-in service used to bypass sanitization if you explicitly trust a source using methods like bypassSecurityTrustHtml() or bypassSecurityTrustUrl().

Code Example


import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'app-secure-render',
  standalone: true,
  template: `<div [innerHTML]="trustedHtml"></div>`
})
export class SecureRenderComponent {
  trustedHtml: SafeHtml;

  constructor(private sanitizer: DomSanitizer) {
    const rawHtml = '<b>Formatted</b> <script>alert("xss")</script>';
    // Sanitizer bypass allows raw html but requires developer responsibility!
    this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(rawHtml);
  }
}
Interviewer Note: 💡 Candidates should demonstrate strict caution when using DomSanitizer, confirming they validate user inputs beforehand.

Question #53: Explain Content Projection in Angular and how to use <ng-content>.

Level: Junior | Category: Components | Time: 5 mins

Expected Answer

Content Projection is a pattern that lets you insert dynamic HTML content from a parent component into a child component's template. This is accomplished using the <ng-content> tag, which acts as a placeholder.

  • Single-slot projection: All projected markup lands inside a single <ng-content> placeholder.
  • Multi-slot projection: You can define multiple slots using the select attribute (e.g. select="[header]", select=".footer").

Code Example


@Component({
  selector: 'app-panel',
  standalone: true,
  template: `
    <div class="panel-container">
      <div class="panel-header">
        <ng-content select="[header]"></ng-content>
      </div>
      <div class="panel-body">
        <ng-content></ng-content> <!-- Default Slot -->
      </div>
    </div>
  `
})
export class PanelComponent {}
Interviewer Note: 💡 A junior candidate should understand why content projection is essential for creating reusable wrapper components like cards, modals, or accordions.

Question #54: How do you handle HTTP errors using RxJS catchError in HttpClient?

Level: Junior | Category: HttpClient | Time: 5 mins

Expected Answer

Angular's HttpClient returns RxJS Observables. To handle errors, you pipe the request through the catchError operator, which intercept failures (e.g. 404, 500, network loss) and allows you to return a fallback value or throw a refined error message using throwError.

Code Example


import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private http = inject(HttpClient);

  fetchData() {
    return this.http.get('/api/resource').pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('API Error occurred:', error.message);
        return throwError(() => new Error('Something went wrong. Please try again.'));
      })
    );
  }
}
Interviewer Note: 💡 Candidates should mention HttpErrorResponse and distinguish between client-side network errors and server-side HTTP status errors.

Question #55: What are Angular Signals? Explain read-only signals, writeable signals, computed, and effect.

Level: Mid-Level | Category: Signals | Time: 8 mins

Expected Answer

Signals are a reactive system introduced to track state changes and trigger updates efficiently. A signal is a wrapper around a value that notifies interested consumers when that value changes.

  • Writable Signals: Values that can be updated directly using set(), update(), or mutating arrays.
  • Computed Signals: Derived signals created using computed(). They track other signal dependencies and automatically re-calculate. They are read-only and lazy-evaluated (cached).
  • Effects: Functions declared using effect() that run side-effects (e.g., logging, saving to localstorage) whenever their dependent signals change.

Code Example


import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <button (click)="increment()">Clicks: {{ count() }}</button>
    <p>Double count: {{ doubleCount() }}</p>
  `
})
export class CounterComponent {
  count = signal(0);
  doubleCount = computed(() => this.count() * 2);

  constructor() {
    effect(() => {
      console.log('Current count value:', this.count());
    });
  }

  increment() {
    this.count.update(c => c + 1);
  }
}
Interviewer Note: 💡 Check if the candidate knows computed signals are read-only and that you shouldn't modify other signal states inside an effect.

Question #56: Explain the difference between an Observable and a Subject / BehaviorSubject in RxJS.

Level: Mid-Level | Category: RxJS | Time: 7 mins

Expected Answer

In RxJS, these primitives handle data flow differently:

  • Observable: Unicast. The stream execution starts from scratch for each subscriber. Ideal for HTTP requests where you want a fresh request per subscription.
  • Subject: Multicast. Act as both an Observable and an Observer. It maintains a list of subscribers and broadcasts emissions to all active subscribers simultaneously (hot).
  • BehaviorSubject: A type of Subject that requires an initial value. It retains the latest emitted value, and emits it immediately to any new subscriber.

Code Example


import { Subject, BehaviorSubject } from 'rxjs';

// Subject (no initial value, does not cache)
const searchSubject = new Subject<string>();
searchSubject.next('no one hears this');

// BehaviorSubject (caches state)
const userSubject = new BehaviorSubject<string>('Guest');
userSubject.subscribe(user => console.log(user)); // logs 'Guest' immediately
userSubject.next('Admin'); // logs 'Admin'
Interviewer Note: 💡 Strong candidates explain why BehaviorSubjects are perfect for holding application state like user profile data or cart contents.

Question #57: What are Route Guards in Angular? Explain canActivate, canDeactivate, and canMatch.

Level: Mid-Level | Category: Routing | Time: 8 mins

Expected Answer

Route Guards are functional boundaries used to grant or deny access to specific application routes based on condition checks (like login state or role clearances).

  • canActivate: Determines if a user can navigate to a route. Commonly used to check if a user is authenticated.
  • canDeactivate: Checks if a user can leave the current route. Very useful to warn users about unsaved form progress.
  • canMatch: Controls whether a route should match. If it returns false, Angular skips this route mapping entirely. Essential for lazy-loading feature bundles because it prevents loading module assets if clearance fails.

Code Example


import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isAuthenticated()) {
    return true;
  }
  return router.createUrlTree(['/login']);
};
Interviewer Note: 💡 Highlight the difference between canActivate and canMatch (canMatch prevents downloading code files entirely, whereas canActivate executes after bundle download).

Question #58: How do you create custom form validators in Reactive Forms?

Level: Mid-Level | Category: Forms | Time: 7 mins

Expected Answer

A custom validator is a factory function that returns a validation function of type ValidatorFn. The validator function accepts an AbstractControl and returns validation errors (ValidationErrors) if validation fails, or null if it passes.

Code Example


import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

// Factory function allowing parameter input
export function blockDomainsValidator(blockedDomains: string[]): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    if (!control.value) return null;
    const email = control.value as string;
    const domain = email.substring(email.lastIndexOf('@') + 1);
    
    if (blockedDomains.includes(domain)) {
      return { blockedDomain: { domain } };
    }
    return null;
  };
}
Interviewer Note: 💡 Candidates should show how custom validators are added to FormControl configurations and how to access validation errors in the HTML.

Question #59: What are HTTP Interceptors and how do you use them to attach JWT auth tokens?

Level: Mid-Level | Category: HttpClient | Time: 8 mins

Expected Answer

HTTP Interceptors act as middlewares that intercept, modify, or log outgoing HTTP requests and incoming responses globally. Because HttpRequest objects are immutable, interceptors clone the request and update headers (like adding authorization Bearer tokens) before passing the execution flow to the next handler.

Code Example


import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const token = authService.getToken();

  if (token) {
    const cloned = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next(cloned);
  }

  return next(req);
};
Interviewer Note: 💡 Functional interceptors are preferred in modern Angular 15+ setups. Make sure they mention registering it in provideHttpClient(withInterceptors([authInterceptor])).

Question #60: Explain dynamic component loading in Angular. How do you load a component programmatically using ViewContainerRef?

Level: Mid-Level | Category: Components | Time: 8 mins

Expected Answer

Dynamic Component Loading is rendering components on the fly rather than writing their selectors directly in HTML templates. This is accomplished by injecting ViewContainerRef, which acts as a container, and calling its createComponent() method, passing the target component class.

Code Example


import { Component, ViewChild, ViewContainerRef, OnInit } from '@angular/core';
import { DynamicWidgetComponent } from './dynamic-widget.component';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `
    <button (click)="loadWidget()">Load Widget</button>
    <div #widgetContainer></div>
  `
})
export class DashboardComponent {
  @ViewChild('widgetContainer', { read: ViewContainerRef }) container!: ViewContainerRef;

  loadWidget() {
    this.container.clear();
    const componentRef = this.container.createComponent(DynamicWidgetComponent);
    // You can set input properties directly on the instance
    componentRef.instance.widgetId = 'W101';
  }
}
Interviewer Note: 💡 A good candidate discusses cleaning up the container using container.clear() to prevent memory accumulation.

Question #61: Explain the difference between forkJoin, combineLatest, and zip in RxJS.

Level: Mid-Level | Category: RxJS | Time: 7 mins

Expected Answer

These combination operators handle multiple asynchronous streams differently:

  • forkJoin: Similar to Promise.all. It waits for all source observables to complete, then emits a single array of their final emitted values. If any source stream fails, the entire forkJoin fails.
  • combineLatest: Waits for all streams to emit at least once, and then emits an array containing the latest value of each stream whenever any stream emits.
  • zip: Emits values as arrays matching elements by position index (1st from A with 1st from B, 2nd from A with 2nd from B). Good for sequential step pairs.

Real World Scenario

  • Use forkJoin when sending concurrent HTTP fetch requests on application start where you need all data loaded to proceed.
  • Use combineLatest when updating filters or search queries where changing any query parameter needs to trigger a new search including all filter values.
Interviewer Note: 💡 Ensure the candidate notes that forkJoin requires all source streams to complete to emit, whereas combineLatest does not.

Question #62: What are Route Resolvers and how do they help in pre-fetching data before route transition?

Level: Mid-Level | Category: Routing | Time: 7 mins

Expected Answer

Route Resolvers pre-fetch data from APIs before a route transition completes. If the resolver fails or takes too long, the navigation is held back. This avoids flickering rendering where components render with empty states while waiting for HTTP responses.

Code Example


import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { ProductService } from './product.service';

export const productResolver: ResolveFn<any> = (route, state) => {
  const productService = inject(ProductService);
  const productId = route.paramMap.get('id')!;
  return productService.getProduct(productId);
};

// Accessing in Component:
// route.data.subscribe(data => this.product = data.product);
Interviewer Note: 💡 While resolvers improve aesthetic transition states, candidates should know they can block UI updates, causing page load delays. Skeletal loading states are often preferred instead.

Question #63: How does the @defer control flow block work in Angular 17+? Detail its triggers.

Level: Mid-Level | Category: Performance Optimization | Time: 8 mins

Expected Answer

The @defer block allows deferred (lazy) loading of components, directives, and pipes. It splits assets into separate JavaScript bundles loaded only when specified conditions are met.

  • on idle (default): Loads bundle when browser enters idle state.
  • on viewport: Loads bundle when the content scrolls into view.
  • on hover: Loads bundle when the user hovers over placeholder elements.
  • on interaction: Loads when user clicks or types on the placeholder.
  • on timer(time): Loads after a specific delay.
  • when [condition]: Custom boolean expression (like a state signal).

Code Example


@defer (on viewport; prefetch on idle) {
  <app-heavy-chart [data]="stats"></app-heavy-chart>
} @placeholder {
  <div class="chart-skeleton">Loading graph...</div>
} @loading {
  <app-spinner></app-spinner>
} @error {
  <p>Failed to load chart asset.</p>
}
Interviewer Note: 💡 Check if the candidate knows that only components referenced inside @defer that are standalone can be lazy-loaded.

Question #64: Explain the difference between takeUntil and takeUntilDestroyed in RxJS subscription management.

Level: Mid-Level | Category: RxJS | Time: 7 mins

Expected Answer

Both operators manage subscription lifecycles to prevent memory leaks, but differ in syntax complexity:

  • takeUntil: Requires manually creating a notifier subject (e.g. destroy$), piping it to all observables, and triggering destroy$.next() inside the ngOnDestroy() lifecycle method.
  • takeUntilDestroyed(): A modern helper introduced in Angular 16. It handles cleanup automatically under the hood using the active DestroyRef instance, removing boilerplate.

Code Example


import { Component, OnInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-stats',
  standalone: true,
  template: `<p>Stats Active</p>`
})
export class StatsComponent implements OnInit {
  private http = inject(HttpClient);

  constructor() {
    // If used in constructor, it automatically infers active Injection Context
    this.http.get('/api/live').pipe(
      takeUntilDestroyed()
    ).subscribe(data => console.log(data));
  }

  ngOnInit() {
    // If used outside constructor, you must pass active DestroyRef explicitly!
  }
}
Interviewer Note: 💡 Critical check: The candidate must mention that if takeUntilDestroyed() is called outside the constructor (injection context), you must pass DestroyRef explicitly.

Question #65: How do you pass data between routes using router parameters, query parameters, and route data?

Level: Mid-Level | Category: Routing | Time: 6 mins

Expected Answer

Angular Router provides three primary methods for data sharing:

  • Route Parameters: Embedded path variables, e.g., /users/:id. Best for accessing primary entity identifiers.
  • Query Parameters: Optional key-value variables appended at the end, e.g., /search?term=angular&page=2. Shared across multiple views.
  • Route Data: Static properties passed directly in the router configurations, e.g., data: { title: 'Admin panel' }.

Modern Binding: Using withComponentInputBinding(), Angular binds parameters directly to component inputs.

Code Example


// Route config
export const routes = [
  {
    path: 'details/:id',
    component: DetailsComponent,
    data: { role: 'moderator' }
  }
];

// Component matching config
@Component({
  selector: 'app-details',
  standalone: true,
  template: `<p>Id: {{ id }}, Role: {{ role }}</p>`
})
export class DetailsComponent {
  @Input() id!: string;       // Route Parameter
  @Input() role!: string;     // Route Data
  @Input() page?: string;     // Query Parameter
}
Interviewer Note: 💡 Look for candidate's usage of modern component input bindings for routing parameter consumption, introduced in Angular 16.

Question #66: What is View Encapsulation in Angular? Contrast Emulated, None, and ShadowDom.

Level: Mid-Level | Category: Components | Time: 7 mins

Expected Answer

View Encapsulation scopes component CSS styles to protect them from bleeding out and affecting global layouts.

  • Emulated (Default): Angular parses CSS and appends unique attributes (like _ngcontent-c12) to target selectors. This emulates Shadow DOM scoping across all browsers.
  • None: Component CSS is loaded globally, which can bleed into other component templates.
  • ShadowDom: Uses the browser's native Shadow DOM. Creates an isolated shadow boundary that completely locks style inheritance, but some global utility libraries may struggle to target inside.

Code Example


import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-custom-btn',
  standalone: true,
  encapsulation: ViewEncapsulation.ShadowDom,
  styles: [`
    button { background: red; }
  `],
  template: `<button>Scoped Button</button>`
})
export class CustomBtnComponent {}
Interviewer Note: 💡 Check if the candidate knows when to use None (e.g. wrapper themes targeting dynamic child overlays) and its associated risks.

Question #67: What is Zoneless Angular? How does Angular run without Zone.js, and what role do Signals play in this architecture?

Level: Senior | Category: Change Detection | Time: 12 mins

Expected Answer

Traditionally, Angular relies on Zone.js to intercept all asynchronous events (clicks, timers, HTTP calls) and trigger a top-down dirty check of the component tree. Zoneless Angular eliminates Zone.js, removing startup overhead and browser monkey-patch dependency.

  • Configuration: Enabled via provideExperimentalZonelessChangeDetection() in modern bootstrap files.
  • Change Trigger: Angular requires signals or explicit API requests (like Async pipe subscription triggers or ChangeDetectorRef calls) to know when and where updates are needed.
  • Role of Signals: Signals act as dependency tracking alerts, telling Angular exactly which component subtree requires re-rendering, avoiding global DOM updates.

Performance Benefits

  • Lighter Bundle: Reduces initial JS bundle size by ~13-15KB.
  • Faster Performance: Avoids checking components that did not change state, preventing performance bottlenecks in highly interactive dashboards.
  • Easier Debugging: Simpler call stacks. Error logs are no longer wrapped in complex zone execution stacks.

Code Example


// app.config.ts
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
};
Interviewer Note: 💡 This is a crucial concept for Angular 18/19. Strong candidates explain how change detection shifts from top-down polling to reactive target re-rendering.

Question #68: Explain the state management architecture of @ngrx/signals (Signal Store) and how it compares to Redux.

Level: Senior | Category: State Management | Time: 12 mins

Expected Answer

@ngrx/signals is a lightweight, functional state management library based on Angular Signals. It defines state as slices of signals, using a modular design that lets you attach state, selectors, and methods cleanly.

  • Redux Boilerplate: Traditional NgRx (Redux) requires Actions, Reducers, Selectors, and Effects, leading to massive boilerplate.
  • Signal Store Alternative: Declared using signalStore(). It encapsulates slices, provides auto-computed selectors, and leverages custom functional extensions (e.g. withEntities()) for entity manipulation.

Code Example


import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
import { computed } from '@angular/core';

export const CounterStore = signalStore(
  { providedIn: 'root' },
  withState({ count: 0 }),
  withComputed(({ count }) => ({
    doubleCount: computed(() => count() * 2)
  })),
  withMethods((store) => ({
    increment() {
      // Safe state updates
      patchState(store, { count: store.count() + 1 });
    }
  }))
);
Interviewer Note: 💡 Look for comments on local vs global scope. NgRx Signal Store can be declared locally inside component providers to align with component lifecycles.

Question #69: How do you optimize Angular bundles? Detail tree-shaking, code-splitting, ESBuild/Vite, and budgets.

Level: Senior | Category: Performance Optimization | Time: 12 mins

Expected Answer

Optimizing bundles guarantees fast Page Load and First Contentful Paint. Key strategies include:

  • Tree-shaking: Ensure standalone component design, allowing webpack/esbuild to drop unused modules.
  • Code-splitting: Configure lazy-loaded routing structures using dynamic imports (loadComponent).
  • Deferred Loading: Use the @defer control flow syntax to lazy-load elements (such as heavy charts, models) on triggers.
  • Vite/ESBuild Compiler: Adopt modern angular builders (introduced in v17) which execute compilations significantly faster and produce cleaner outputs.
  • Build Budgets: Enforce bundle boundaries inside angular.json configurations, throwing warnings or errors if code sizes grow.

Best Practices

  • Use source-map-explorer to analyze production bundle sizes.
  • Isolate vendor imports. Avoid bringing in full lodash or moment.js packages. Use tree-shakeable equivalents (like date-fns).
Interviewer Note: 💡 Strong candidates explain how ESBuild builders handle css/js pre-bundling more efficiently than Webpack.

Question #70: Explain the concept of Content Security Policy (CSP) and how to configure it in an Angular application rendering with SSR.

Level: Senior | Category: Security | Time: 12 mins

Expected Answer

Content Security Policy (CSP) is an HTTP header protocol that restricts external domains from injecting inline scripts or malicious styles. In SSR contexts, inline style blocks and hydration bootstrap tags generated dynamically by the server fail CSP checks unless they are signed.

  • Nonces: A unique cryptographic string generated per request on the SSR server, attached to CSP headers, and applied to inline script/style tags.
  • Angular Nonce Provider: Register the token value in server configuration providers so Angular automatically inserts the nonce tag onto compiled page elements.

Code Example


// server.ts (Express configuration)
// app.get('*', (req, res) => {
//   const nonce = generateSecureNonce();
//   res.setHeader('Content-Security-Policy', `default-src 'self'; script-src 'self' 'nonce-${nonce}';`);
//   
//   res.render('index', {
//     req,
//     providers: [{ provide: CSP_NONCE, useValue: nonce }]
//   });
// });
Interviewer Note: 💡 This is an advanced security scenario. Check if they know CSP_NONCE from @angular/core is used to sync tokens.

Question #71: How do you write unit tests for Angular components using Signals, Signal inputs/outputs, and Effects?

Level: Senior | Category: Testing | Time: 10 mins

Expected Answer

Testing signals is straightforward since they are accessed via simple function calls. In tests:

  • Readable Signals: Access signal properties directly like component.mySignal() and match expectations.
  • Signal Inputs: Set inputs programmatically in tests using fixture.componentRef.setInput('inputName', value).
  • Signal Outputs: Subscribe to output emissions via testing event emitters or mock listeners.
  • Effects: Component effects execute during change detection cycles. Trigger fixture.detectChanges() or inject TestBed.flushEffects() to trigger effect updates.

Code Example


// fixture.componentRef.setInput('username', 'Alice');
// fixture.detectChanges();
// expect(component.welcomeMessage()).toBe('Welcome, Alice!');
Interviewer Note: 💡 Check if the candidate knows how to mock signals and use setInput (since inputs are read-only and cannot be mutated directly).

Question #72: What is Angular CDK, and how does it help build accessible, highly customizable UI components?

Level: Senior | Category: CDK | Time: 10 mins

Expected Answer

The Angular Component Dev Kit (CDK) is a library containing behavior primitives and structural utilities without layout styles. It solves complex UI interactions (accessibility, rendering performance, layout behaviors) while giving developers complete freedom over styling.

  • Accessibility (A11y): Provides keyboard navigation helpers, focus traps, and screen reader announcements.
  • Overlay Service: Coordinates modal placements, dropdown floats, tooltips, and overlay containers.
  • Virtual Scrolling: Renders massive lists by recycling DOM elements as they scroll out of view.

Code Example


import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Injectable, inject } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ModalService {
  private overlay = inject(Overlay);

  open(component: any): OverlayRef {
    const overlayRef = this.overlay.create({
      hasBackdrop: true,
      positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically()
    });
    overlayRef.attach(new ComponentPortal(component));
    return overlayRef;
  }
}
Interviewer Note: 💡 Candidates should talk about separation of concerns: Angular CDK handles the logic and compliance, while custom CSS handles presentation.

Question #73: Explain the transition from Angular Universal to the modern @angular/ssr package. What changed in Angular 17+?

Level: Senior | Category: SSR | Time: 11 mins

Expected Answer

In Angular 17, the legacy Angular Universal compiler was integrated into the core Angular framework under @angular/ssr.

  • Build Tooling: Moves from Webpack to ESBuild/Vite, compiling client and server assets concurrently.
  • Hydration: Features Non-destructive Hydration out of the box, linking logic to server HTML without DOM flickering.
  • Simplified Setup: Replaces configuration files with standard configurations, utilizing provideServerRendering().

Performance Improvements

  • Faster builds: Concatenates build times by up to 5x.
  • Better SEO score: Eliminates DOM flickering, raising Lighthouse scores.
Interviewer Note: 💡 Ensure the candidate can outline the differences between static prerendering and dynamic server rendering.

Question #74: How do you build dynamic forms with schema-driven validation in Reactive Forms?

Level: Senior | Category: Forms | Time: 12 mins

Expected Answer

Dynamic Forms generate controls dynamically from a config object (e.g. JSON schema) rather than hardcoding fields. Iterate through the schema definition, instantiate FormControls, add validation objects based on schema configurations, and register them into a root FormGroup.

Code Example


import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';

interface FormFieldConfig {
  name: string;
  type: string;
  required: boolean;
}

@Component({
  selector: 'app-schema-form',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="dynamicForm">
      @for (field of schema; track field.name) {
        <div>
          <label>{{ field.name }}</label>
          <input [type]="field.type" [formControlName]="field.name" />
        </div>
      }
    </form>
  `
})
export class SchemaFormComponent implements OnInit {
  schema: FormFieldConfig[] = [
    { name: 'username', type: 'text', required: true },
    { name: 'age', type: 'number', required: false }
  ];
  dynamicForm = new FormGroup({});

  ngOnInit() {
    this.schema.forEach(field => {
      const rules = field.required ? [Validators.required] : [];
      this.dynamicForm.addControl(field.name, new FormControl('', rules));
    });
  }
}
Interviewer Note: 💡 Check if the candidate knows how dynamic configurations affect component re-rendering and the benefits of addControl / removeControl.

Question #75: Explain the difference between Signal Inputs (input()), Signal Outputs (output()), and the traditional @Input/@Output decorators.

Level: Senior | Category: Signals | Time: 11 mins

Expected Answer

Signal inputs and outputs modernize parent-child communication:

  • Signal Inputs (input()): Values are received as Signals. Instead of implementing ngOnChanges to trace changes, you can declare computed() signals or trigger effects directly when values change.
  • Signal Outputs (output()): Clean alternative to @Output() and EventEmitter. Fully type-safe and does not use RxJS EventEmitters under the hood, simplifying the rendering cycle.

Code Example


import { Component, input, output, computed } from '@angular/core';

@Component({
  selector: 'app-signal-child',
  standalone: true,
  template: `<button (click)="emit()">Click {{ formattedVal() }}</button>`
})
export class SignalChildComponent {
  // Signal Input
  value = input.required();
  // Computed derived from Input
  formattedVal = computed(() => `Value: ${this.value() * 10}`);
  
  // Signal Output
  action = output();

  emit() {
    this.action.emit('clicked');
  }
}
Interviewer Note: 💡 Signal inputs are read-only (you cannot assign value = 5 directly). Ask candidates how to handle fallback values.

Question #76: How do you handle deep nested API orchestration using RxJS operators in a complex workflow?

Level: Senior | Category: RxJS | Time: 12 mins

Expected Answer

Complex async flows require piping operators to ensure reliability and clean error flows:

  • Orchestration: Use flatMap mapping operators (switchMap for cancellations, concatMap for strict sequencing).
  • Resilience: Pipe retry({ count: 3, delay: 1000 }) to recover from transient failures.
  • State Sharing: Use shareReplay(1) to cache HTTP responses and avoid redundant calls to the backend.
  • Error boundaries: Wrap nested streams with internal catchError blocks to prevent global streams from closing.

Code Example


import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { switchMap, shareReplay, retry, catchError } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class OrchestrationService {
  private http = inject(HttpClient);

  getUserProfileAndOrders(userId: string) {
    return this.http.get(`/api/users/${userId}`).pipe(
      retry(2),
      switchMap(profile => 
        this.http.get(`/api/orders/${userId}`).pipe(
          catchError(err => {
            console.error('Failed to load orders', err);
            throw err;
          })
        )
      ),
      shareReplay(1)
    );
  }
}
Interviewer Note: 💡 Look for the candidate's understanding of catchError placement. Placing catchError on the inner stream preserves the outer stream.

Question #77: How would you implement micro-frontends in Angular using Webpack Module Federation?

Level: Senior | Category: Architecture | Time: 12 mins

Expected Answer

Module Federation allows compiling distinct Angular applications independently and loading them dynamically at runtime.

  • Host App: The shell container that resolves routing pathways and loads remote bundles dynamically.
  • Remote Apps: Feature applications that expose entry points (components/modules) via config files.
  • Shared Libraries: Share libraries like @angular/core, @angular/common, and rxjs as singletons, ensuring the browser only downloads a single copy.

Router Configuration


// router config in Host Shell Application
// loadChildren: () => loadRemoteModule({ type: 'module', remoteEntry: '...', exposedModule: './Module' }).then(m => m.Module)
Interviewer Note: 💡 Make sure they discuss version compatibility challenges (ensuring all micro-frontends use matching core Angular versions).

Question #78: How do you configure environment variables dynamically at runtime instead of build-time in Angular?

Level: Senior | Category: Build System | Time: 11 mins

Expected Answer

Build-time variables require rebuilding the application for every environment (Dev, QA, Prod). To achieve dynamic runtime configuration:

  • JSON Asset: Store configuration values in a static file (e.g. assets/config.json).
  • APP_INITIALIZER: Inject a service that fetches this configuration file using HttpClient during application initialization, blocking execution until the request resolves.
  • Deployment: The deployment pipeline modifies the config.json file at runtime on the server (e.g., using Kubernetes ConfigMaps).

Code Example


// APP_INITIALIZER provider mapping config:
// { provide: APP_INITIALIZER, useFactory: initializeApp, deps: [HttpClient], multi: true }
Interviewer Note: 💡 Ask the candidate how this strategy affects SSR. (Answer: The server fetch endpoint must also resolve local configurations correctly).

Architect Question #6: Design a scalable state management architecture for a multi-tenant corporate banking application.

Level: Architect | Category: State Management | Time: 15 mins

Expected Answer

A multi-tenant corporate banking portal requires clean state boundaries, strict security isolations, and fast performance.

  • State Separation: Define state into three levels:
    • Global State: Session validation tokens, tenant settings.
    • Feature Scope: Transaction entries, customer search profiles.
    • Local Scope: Component UI open/close status.
  • Tenant Isolation: Dynamically clean state during tenant swaps to prevent data leakage.
  • Signal Stores: Use NgRx Signal Store with custom extensions to fetch and apply configurations on routing boundaries.

Architecture Blueprint


// Multi-level Signal state model representation
// export const AuthStore = signalStore({ providedIn: 'root' }, ...)
Interviewer Note: 💡 Look for discussion on security: ensuring client memory cleanup during logout/tenant switches to protect user banking data.

Architect Question #7: How do you apply Clean Architecture and SOLID principles to an enterprise-scale Angular project?

Level: Architect | Category: Architecture | Time: 15 mins

Expected Answer

Clean Architecture separates business rules from frameworks and interfaces, ensuring modularity and testability.

  • Presentation Layer: Dumb (presentational) components rendering HTML/CSS, interacting strictly via inputs/outputs.
  • Application Layer: Smart (container) components orchestrating state, injecting services and signals.
  • Domain (Core) Layer: Pure TypeScript services, interfaces, models, and utility calculations. Pure business rules, free of direct Angular UI references.
  • Infrastructure Layer: HttpClient connectors, storage bindings, API mappings, and interceptors.

SOLID Implementation in Angular

  • Single Responsibility: Components handle UI. Data loading is delegated to services.
  • Open/Closed: Use Host Directives to extend component behavior without modifying the component source.
  • Dependency Inversion: Depend on token abstractions rather than concrete service classes, allowing easy mocking in unit tests.
Interviewer Note: 💡 Look for the candidate's use of clean architecture layers in file structures (e.g. core vs shared vs features).

Architect Question #8: Detail the architecture of an Nx Monorepo with multiple Angular applications sharing libraries. How do you enforce boundaries?

Level: Architect | Category: Architecture | Time: 15 mins

Expected Answer

An Nx Monorepo helps coordinate multiple applications and shared libraries under a single repository, enforcing coding standards across teams.

  • Library Classification: Categorize libraries into distinct types:
    • feature: Route pages and smart container logic.
    • ui: Pure reusable components (design system).
    • data-access: Services, state, HttpClient endpoints.
    • utils: Pure helper functions, formatting utilities.
  • Lint Tag Constraints: Use Nx boundary configurations in ESLint to restrict dependencies. For example, prevent a ui library from importing a feature library, and enforce domain isolation (e.g. domain:finance cannot import domain:hr).

Boundary Configuration


// eslint rules configuration representation:
// "@nx/enforce-module-boundaries": [ "error", { "depConstraints": [...] } ]
Interviewer Note: 💡 Strong candidates highlight dependency control: preventing dependency cycles and domain pollution across monorepo boundaries.

Architect Question #9: Design a zero-downtime, global localization (i18n) strategy for a high-traffic Angular portal.

Level: Architect | Category: Architecture | Time: 15 mins

Expected Answer

Zero-downtime internationalization demands low build times, fast CDN load speeds, and dynamic language switching.

  • Build-time Localization (@angular/localize): Compiles a separate application bundle for each target language. This is fast and optimized, but switching languages requires a full page reload.
  • Runtime Localization (Transloco/ngx-translate): Fetches translations at runtime as JSON files. This allows instant, reload-free language changes, but introduces network overhead and layout shifts.
  • Recommended Architecture: Use build-time localization with CDN-routed URL paths (e.g., domain.com/es/, domain.com/en/). Combine this with client-side routing redirects to serve localized pages instantly based on request headers.
Interviewer Note: 💡 Ensure they cover SEO considerations: localized bundles are indexed correctly by search engine crawlers.

Architect Question #10: Design a real-time collaborative document editing system in Angular. How do you handle synchronization, state consistency, and rendering performance?

Level: Architect | Category: Architecture | Time: 15 mins

Expected Answer

Collaborative systems require low-latency synchronization and highly optimized rendering to prevent typing lag.

  • Network & Conflict Resolution: Implement WebSockets paired with Conflict-free Replicated Data Types (CRDTs) like Yjs or Automerge to resolve editing conflicts.
  • Zoneless Keystroke Sync: Run key-event listeners outside Zone.js (or in Zoneless mode) to prevent typing actions from triggering full change detection loops across the application.
  • Rendering Optimization: Use Virtual Scrolling to render only the visible viewport paragraphs, recycling DOM elements during rapid scrolls.
Interviewer Note: 💡 Strong candidates explain why Zone.js must be bypassed during rapid local typing updates to maintain UI responsiveness.

Architect Question #11: How do you design an error-logging and performance-monitoring architecture for a large Angular app?

Level: Architect | Category: Architecture | Time: 15 mins

Expected Answer

Enterprise monitoring requires capturing client-side errors and performance metrics without degrading application performance.

  • ErrorHandler Provider: Override the default ErrorHandler provider to intercept all uncaught browser exceptions globally.
  • Context Enrichment: Gather diagnostic information (e.g., active route, user session state, recent UI events, API response codes).
  • Batch Uploads: Buffers logs and uploads them in batches or during browser idle times to minimize network overhead.

Code Example


import { ErrorHandler, Injectable, Injector } from '@angular/core';

@Injectable()
export class GlobalLoggingHandler implements ErrorHandler {
  constructor(private injector: Injector) {}
  handleError(error: any): void {
    console.error('Logged Error:', error.message || error.toString());
  }
}
Interviewer Note: 💡 Ask the candidate: 'Why do we inject Injector instead of services directly?' (Answer: To prevent circular dependencies, since logging handler is created at application startup).

Architect Question #12: What is the rendering strategy selection framework (SSG, SSR, CSR, Prerendering) for a complex portal?

Level: Architect | Category: SSR | Time: 15 mins

Expected Answer

Select the best rendering strategy based on SEO needs and data dynamic nature:

  • SSG (Static Site Generation): Best for static pages (e.g., Terms of Service, landing pages). Loaded instantly from CDNs.
  • SSR (Server-Side Rendering): Best for public-facing dynamic pages (e.g., e-commerce products, job listings). Pre-renders content on the server for search engine crawlers.
  • CSR (Client-Side Rendering): Best for secure, authenticated portals (e.g., user dashboards, settings). Avoids SSR overhead since content is hidden behind a login page.
Interviewer Note: 💡 Look for candidate's understanding of hybrid approaches, compiling public paths to static pages while routing dashboard pages to client-side rendering.

Architect Question #13: Explain the architectural differences and migration roadmap when moving a legacy Angular 14 codebase with NgModules to a modern Zoneless Angular 19+ standalone application.

Level: Architect | Category: Architecture | Time: 15 mins

Migration Roadmap

  1. Upgrade CLI and Node: Progressively upgrade Angular packages (e.g., v14 -> v16 -> v18 -> v19) using ng update to avoid breaking changes.
  2. Migrate to Standalone: Run ng generate @angular/core:standalone to convert components and remove NgModules.
  3. Adopt Signals: Replace RxJS state containers inside components with Signals and Computed properties.
  4. Remove Zone.js: Remove zone.js from polyfills. Add provideExperimentalZonelessChangeDetection() in config files, then test for change detection errors.
Interviewer Note: 💡 Check if the candidate recommends incremental migration (feature-by-feature) rather than a complete rewrite.

Angular Trap #9: Why do Signals not require cleanup for effects, but RxJS subscriptions do? What is the execution lifecycle of effect()?

Level: Tricky Question | Category: Signals | Time: 10 mins

Expected Answer

Effects automatically manage their life cycle based on the context they are declared in:

  • Automatic Cleanup: When declared in components or services, effect() links to their active injection context. When that component is destroyed, the effect is destroyed automatically.
  • Manual RxJS Cleanup: RxJS subscriptions are not bound to component lifecycle contexts. If a component subscribes to a global service observable without unsubscribing, the subscription remains active, leaking memory.
  • Effect Cleanup API: The effect callback passes an onCleanup registration method, allowing you to clean up external assets (e.g. timeout triggers, canvas frames) before the next run.

Code Example


effect((onCleanup) => {
  const token = setTimeout(() => console.log('Ping'), 1000);
  onCleanup(() => clearTimeout(token));
});
Interviewer Note: 💡 Explain that effects shouldn't write to signals by default to avoid cascading change detection loops (throws errors unless `allowSignalWrites: true` is set).

Angular Trap #10: Why does inject() only work during execution of injection contexts, and how can you run it asynchronously?

Level: Tricky Question | Category: Dependency Injection | Time: 10 mins

Expected Answer

The inject() function relies on a global, active injector context that is set during class creation (e.g. constructor execution or field initialization). If you call inject() inside an asynchronous callback (like a setTimeout or click event handler), that initialization context is lost, throwing an error.

Solution: Store a reference to the active Injector during constructor initialization, then wrap the async logic with runInInjectionContext().

Code Example


import { Component, Injector, runInInjectionContext, inject } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-async-inject',
  standalone: true,
  template: `<button (click)="onClick()">Load Async</button>`
})
export class AsyncInjectComponent {
  private injector = inject(Injector);
  onClick() {
    setTimeout(() => {
      runInInjectionContext(this.injector, () => {
        const service = inject(DataService);
      });
    }, 500);
  }
}
Interviewer Note: 💡 Check if the candidate knows how to leverage runInInjectionContext to dynamically load libraries or services at runtime.

Angular Trap #11: Why does a custom validator on a Form Group not run when nested Form Controls are disabled?

Level: Tricky Question | Category: Forms | Time: 10 mins

Expected Answer

When a child FormControl is disabled, it is excluded from the group validation list and its value is removed from FormGroup.value. If all children controls inside a Form Group are disabled, the containing Form Group transitions to the DISABLED state automatically, bypassing all group-level validators. They will not execute, even if validations failed beforehand.

Workaround: Read values using formGroup.getRawValue() instead, and run validations manually, or keep controls enabled but set them as read-only (readonly attribute in HTML).

Interviewer Note: 💡 A tricky form scenario. Strong candidates can explain the difference between control.disable() and element.setAttribute('readonly').

Angular Trap #12: What is the hazard of using inline function calls inside Angular template interpolations?

Level: Tricky Question | Category: Templates | Time: 10 mins

Expected Answer

Inline functions in templates (e.g. {{ calculateResult() }}) execute on every change detection cycle. Even if unrelated state changes (like a menu toggle), Angular executes the function to verify if the output changed. This degrades performance significantly.

  • Pure Pipes: Cache calculation results and only run when their input references change.
  • Computed Signals: Automatically track dependencies and only re-evaluate when those dependencies update, bypassing redundant runs.

Code Example


// Bad Practice:
// <p>Result: {{ expensiveCalculation(userId) }}</p>
// Good Practice using Computed Signals:
// userId = signal('101');
// expensiveResult = computed(() => this.expensiveCalculation(this.userId()));
Interviewer Note: 💡 Check if the candidate knows how OnPush change detection interacts with template function runs. (Answer: Inline functions still run on every check triggered inside that component).

Angular Trap #13: Why does an HTTP Interceptor sometimes run twice for a single router navigation?

Level: Tricky Question | Category: HttpClient | Time: 10 mins

Expected Answer

If an HTTP interceptor runs twice during a single routing action, check for these issues:

  • Resolver + Init Checks: A route resolver sends API requests concurrently with the component's ngOnInit calls.
  • Interceptors hierarchy: If an interceptor is registered in both root configurations and inside lazy-loaded router modules, the request will pass through both instances.
  • HTTP Redirects: 301/302 redirects from the server trigger a second HTTP request, passing through the interceptor pipeline again.
Interviewer Note: 💡 Check if they discuss debugging HTTP flows using Chrome's Network Tab waterfall to trace requests.

Angular Trap #14: Why does ChangeDetectorRef.detectChanges() sometimes trigger ExpressionChangedAfterItHasBeenCheckedError?

Level: Tricky Question | Category: Change Detection | Time: 10 mins

Expected Answer

Calling detectChanges() forces Angular to immediately check the component and its children. If a child updates a parent property (via bindings or lifecycle hooks) as a result of that run, the values will mismatch when the parent is checked next, throwing the error.

  • Solution: Use markForCheck(), which flags the component as dirty without forcing immediate, synchronous execution.
  • Alternative: Wrap the state changes in a microtask (Promise.resolve().then()) to defer execution to the next cycle.
Interviewer Note: 💡 Candidates should know that detectChanges() forces immediate rendering, whereas markForCheck() schedules it for the next tick.

Angular Trap #15: How does the Router's runGuardsAndResolvers option impact performance and how can it lead to loops?

Level: Tricky Question | Category: Routing | Time: 10 mins

Expected Answer

By default, route guards and resolvers execute only when route paths change. If you set runGuardsAndResolvers: 'always', they execute on query parameter changes as well.

The Trap: If a guard or resolver updates query parameters (e.g., setting default pagination query params like ?page=1) as part of its execution logic, it will trigger another routing change, causing an infinite loop.

Interviewer Note: 💡 Good candidates suggest using skipLocationChange or checking current parameters before updating routing values.

Angular Trap #16: Why does @ContentChildren fail to query nested components projected through multiple levels of intermediate containers?

Level: Tricky Question | Category: Components | Time: 10 mins

Expected Answer

By default, @ContentChildren and @ContentChild only search direct child elements projected into the component. If elements are nested inside other wrappers, they are skipped.

Solution: Pass { descendants: true } in the decorator query configuration to search the entire projected DOM tree recursively.

Code Example


// Default only queries direct children
@ContentChildren(TabItemComponent) tabs!: QueryList<TabItemComponent>;
// Solution queries deep nested tabs
@ContentChildren(TabItemComponent, { descendants: true }) deepTabs!: QueryList<TabItemComponent>;
Interviewer Note: 💡 Check if the candidate knows when content query hooks execute (ngAfterContentInit).

Production Incident #4: E-Commerce Site Hydration Failure due to timezone differences between Server (UTC) and Client browser

Level: Production Incident | Category: SSR | Time: 15 mins

Scenario

An e-commerce site experiences rendering issues and console warnings (e.g. NG0500: Hydration mismatch) in production. Dates and times change or flicker immediately after page load.

Root Cause Analysis

The server rendering engine builds page HTML using UTC timezone dates (e.g. 18:00 UTC). When the client browser parses this, it renders dates in the user's local timezone (e.g. 13:00 EST). This timezone difference creates mismatched HTML structure and text between client and server, causing hydration to fail.

Resolution

  • Ensure all dates are formatted using a fixed UTC timezone or timezone offset before rendering on the server.
  • Use client-only rendering blocks (like <ng-container *browserOnly>) or wrap timezone-dependent date renders in lifecycle checks (e.g., updating state variables only after ngOnInit).
Interviewer Note: 💡 Real hydration debugging experience. Look for clear understanding of the differences between server and client environments.

Production Incident #5: Memory Leak caused by retaining component references inside a global Singleton Service's subject stream

Level: Production Incident | Category: Performance Debugging | Time: 15 mins

Scenario

A customer service portal experiences browser slowdowns and crashes after multiple route transitions. Heap snapshots show component instances are retained in memory long after their DOM elements are destroyed.

Root Cause Analysis

Components register listeners on global, singleton services. If a component subscribes to a global service's RxJS Subject but fails to unsubscribe, the singleton service maintains a reference to the component's callback function in memory, preventing garbage collection.

Resolution

  • Always use takeUntilDestroyed() or the async pipe to automatically manage subscription lifecycles.
  • Use Chrome DevTools to take Heap Snapshots, searching for detached DOM elements and component instances.
Interviewer Note: 💡 Check if the candidate can outline how to analyze memory leaks using Chrome's Heap Profiler.

Production Incident #6: Route Resolvers blocking page rendering, causing a perceived page-load freeze

Level: Production Incident | Category: Routing | Time: 15 mins

Scenario

Users report that clicking navigation links makes the page feel frozen for several seconds before anything updates.

Root Cause Analysis

The route configuration contains resolvers that pre-fetch data from APIs. Because Angular Router blocks navigation until all resolvers emit and complete, slow API responses delay route transitions, leaving users with a frozen UI.

Resolution

  • Configure timeouts using the timeout() operator in resolvers, throwing fallback values if requests take too long.
  • Load critical data lazily in components using skeletal placeholders rather than blocking navigation with resolvers.
Interviewer Note: 💡 Strong candidates explain why lazy loading data at the component level with loading skeletons is preferred over blocking navigation with resolvers.

Production Incident #7: Angular Material Dialog overlay rendering behind the backdrop due to CSS stacking contexts

Level: Production Incident | Category: Components | Time: 15 mins

Scenario

Opening a modal dialog makes the page backdrop grey, but the dialog itself renders behind the backdrop, preventing users from clicking anything.

Root Cause Analysis

The component triggering the dialog has CSS properties that create a new CSS stacking context (e.g. transform, will-change, z-index). This forces the dynamic overlay container to render inside the child stacking context rather than at the root document body level, placing it behind the global backdrop.

Resolution

  • Configure Angular CDK overlays to render directly at the root body level.
  • Avoid using transform or will-change on container components that trigger overlays, or adjust their stacking context rules.
Interviewer Note: 💡 Look for candidate's CSS debugging skills, specifically their understanding of z-index and CSS stacking contexts.

Production Incident #8: Infinite loop of change detection triggered by a service subscribing to state changes and updating the store

Level: Production Incident | Category: Change Detection | Time: 15 mins

Scenario

The browser tab freezes, memory usage spikes, and the console repeatedly throws the error: Infinite change detection loop detected.

Root Cause Analysis

A component template invokes a function that calls a service method. This service updates application state, which triggers another change detection cycle. This cycle re-evaluates the template, calling the function again and creating an infinite loop.

Resolution

  • Avoid executing functions that modify state directly inside template interpolations.
  • Convert template functions to read-only computed() signals to cache calculated values.
Interviewer Note: 💡 Ask the candidate: 'Why do template functions run on every change detection cycle?' (Answer: Because templates are re-evaluated to detect potential value shifts).

Production Incident #9: Angular SSR App crashes under high load when SSR memory usage spikes due to window/document polyfills

Level: Production Incident | Category: SSR | Time: 15 mins

Scenario

A high-traffic website experiences server crashes under heavy load, with logs showing OutOfMemoryError in the Node.js process.

Root Cause Analysis

Node.js does not have browser global variables like window or document. To bypass library import issues, developers use global window polyfills (e.g. Domino). If third-party libraries write data to these global objects, memory accumulates across requests, eventually crashing the server.

Resolution

  • Avoid using global window polyfills in Node.js.
  • Wrap browser-specific code in isPlatformBrowser() checks, or inject mock window references scoped to individual requests.
Interviewer Note: 💡 Excellent SSR test. Candidates should explain why global variables leak memory in multi-request server environments.

Coding Challenge #1: Build a custom debounce directive using RxJS to throttle rapid click actions.

Level: Coding Challenge | Category: Directives | Time: 20 mins

Expected Solution

Create a directive that intercepts user clicks, routes them through an internal RxJS subject with `debounceTime`, and emits a custom output event.

Code Example


import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

@Directive({
  selector: '[appDebounceClick]',
  standalone: true
})
export class DebounceClickDirective implements OnInit, OnDestroy {
  @Input() debounceTime = 500;
  @Output() debouncedClick = new EventEmitter<any>();
  private clicks = new Subject<any>();
  private subscription!: Subscription;
  ngOnInit() {
    this.subscription = this.clicks.pipe(
      debounceTime(this.debounceTime)
    ).subscribe(e => this.debouncedClick.emit(e));
  }
  @HostListener('click', ['$event'])
  clickEvent(event: any) {
    event.preventDefault();
    event.stopPropagation();
    this.clicks.next(event);
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}
Interviewer Note: 💡 Verify they clean up the subscription inside ngOnDestroy to prevent memory leaks.

Coding Challenge #2: Build a custom Structural Directive (*appRoleOnly) for Role-Based Access Control (RBAC)

Level: Coding Challenge | Category: Directives | Time: 20 mins

Expected Solution

Create a structural directive that evaluates user roles. Inject TemplateRef and ViewContainerRef to dynamically render or destroy the template based on clearance.

Code Example


import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angular/core';
import { AuthService } from './auth.service';

@Directive({
  selector: '[appRoleOnly]',
  standalone: true
})
export class RoleOnlyDirective {
  private templateRef = inject(TemplateRef);
  private viewContainer = inject(ViewContainerRef);
  private authService = inject(AuthService);
  @Input() set appRoleOnly(requiredRole: string) {
    if (this.authService.hasRole(requiredRole)) {
      this.viewContainer.clear();
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainer.clear();
    }
  }
}
Interviewer Note: 💡 Check if the candidate knows how structural directives manipulate the DOM using template refs and view container refs.

Coding Challenge #3: Implement a generic State management class using raw Signals and Computed

Level: Coding Challenge | Category: State Management | Time: 20 mins

Expected Solution

Create a class wrapper that stores application state in a private WritableSignal and exposes selectors as read-only Computed signals.

Code Example


import { signal, computed, Signal } from '@angular/core';
export class Store<T> {
  private stateSignal;
  constructor(initialState: T) {
    this.stateSignal = signal<T>(initialState);
  }
  select<K>(mapFn: (state: T) => K): Signal<K> {
    return computed(() => mapFn(this.stateSignal()));
  }
  update(updateFn: (state: T) => Partial<T>) {
    this.stateSignal.update(state => ({
      ...state,
      ...updateFn(state)
    }));
  }
}
Interviewer Note: 💡 Look for clean immutable update patterns inside the update function.

Coding Challenge #4: Implement an HTTP Cache Interceptor that caches GET requests and invalidates them on mutation requests

Level: Coding Challenge | Category: HttpClient | Time: 20 mins

Expected Solution

Create an interceptor that caches GET responses in an in-memory Map. If a user triggers a mutating request (POST, PUT, DELETE) to the same resource, clear the cache to ensure data freshness.

Code Example


import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';
const cacheMap = new Map<string, HttpResponse<any>>();
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
  if (req.method !== 'GET') {
    cacheMap.clear();
    return next(req);
  }
  const cachedResponse = cacheMap.get(req.urlWithParams);
  if (cachedResponse) {
    return of(cachedResponse.clone());
  }
  return next(req).pipe(
    tap(event => {
      if (event instanceof HttpResponse) {
        cacheMap.set(req.urlWithParams, event.clone());
      }
    })
  );
};
Interviewer Note: 💡 Check if the candidate clones the cached response to prevent components from mutating shared cache objects.

Coding Challenge #5: Build a reusable Form Field component with custom control value accessor (NG_VALUE_ACCESSOR)

Level: Coding Challenge | Category: Forms | Time: 20 mins

Expected Solution

Implement the ControlValueAccessor interface, mapping forms control API values (writeValue, registerOnChange, registerOnTouched) to custom inputs.

Code Example


import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
  selector: 'app-custom-input',
  standalone: true,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ],
  template: `<input (input)="onInput($event)" [value]="value" />`
})
export class CustomInputComponent implements ControlValueAccessor {
  value = '';
  onChange = (val: string) => {};
  onTouched = () => {};
  writeValue(value: string): void { this.value = value || ''; }
  registerOnChange(fn: any): void { this.onChange = fn; }
  registerOnTouched(fn: any): void { this.onTouched = fn; }
  onInput(event: any) {
    this.value = event.target.value;
    this.onChange(this.value);
  }
}
Interviewer Note: 💡 A core concept in enterprise Angular forms. Candidates must understand what writeValue, registerOnChange, and registerOnTouched do.

Coding Challenge #6: Create a dynamic tabs component using content projection and QueryList

Level: Coding Challenge | Category: Components | Time: 20 mins

Expected Solution

Build a container component (TabsGroup) that queries nested tab item child directives using @ContentChildren and displays the active tab template.

Code Example


import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { TabItemComponent } from './tab-item.component';
@Component({
  selector: 'app-tabs-group',
  standalone: true,
  template: `
    <div class="tab-headers">
      @for (tab of tabs; track tab.title) {
        <button (click)="selectTab(tab)" [class.active]="tab.active">{{ tab.title }}</button>
      }
    </div>
    <ng-content></ng-content>
  `
})
export class TabsGroupComponent implements AfterContentInit {
  @ContentChildren(TabItemComponent) tabs!: QueryList<TabItemComponent>;
  ngAfterContentInit() {
    const activeTab = this.tabs.find(t => t.active);
    if (!activeTab && this.tabs.first) {
      this.tabs.first.active = true;
    }
  }
  selectTab(tab: TabItemComponent) {
    this.tabs.forEach(t => t.active = false);
    tab.active = true;
  }
}
Interviewer Note: 💡 Check if the candidate knows to use ngAfterContentInit to access ContentChildren.

Anti-AI Scenario #1: Why is using toObservable inside a computed signal definition a fatal architectural flaw?

Level: Anti-AI Validation | Category: Signals vs RxJS | Time: 12 mins

Expected Answer

Using toObservable() inside a computed() signal creates an infinite loop or runtime error.

  • Injection Context Constraint: toObservable() requires an active injection context to look up DestroyRef and manage cleanups. computed() does not provide an injection context.
  • Infinite Loop: computed() is resolved synchronously during evaluation, whereas toObservable() schedules value propagation asynchronously. Mixing these triggers race conditions and state sync errors.

Best Practices

Keep computed() pure and synchronous. If you need to convert signals to observables, call toObservable() in the component's constructor, then pipe operations in RxJS separately.

Interviewer Note: 💡 This question identifies candidates who rely on AI generated code, as AI models frequently mix injection contexts and async helpers inside computed blocks.

Anti-AI Scenario #2: Why does injecting a service within a lazy-routed component constructor sometimes resolve to a different instance than injecting it inside a route guard?

Level: Anti-AI Validation | Category: Dependency Injection | Time: 12 mins

Expected Answer

This injection mismatch occurs when the target service is registered inside route-level providers:

  • Environment Injector: Route-level providers (defined in the route configurations using providers: [...]) instantiate services within the Route's EnvironmentInjector boundary.
  • Route Guards: Resolve dependencies using the injector context that was active when routing began. If the route-scoped injector hasn't been instantiated yet, the guard will fall back and resolve the singleton instance from the root injector instead, resulting in a different instance than the one resolved inside the component.
Interviewer Note: 💡 Tests deep injection hierarchy understanding. Candidates must explain how Environment Injectors differ from Component Injectors.

Anti-AI Scenario #3: Why is the naive implementation of a custom ControlValueAccessor using a Signal for internal state sync buggy when the form is programmatically reset?

Level: Anti-AI Validation | Category: Forms | Time: 12 mins

Expected Answer

When an Angular form is reset, the framework calls writeValue(null) on the component. If the component's internal state signal updates via signal.set(null), it will trigger an output emission change callback (onChange(null)). This tells the parent form that the value changed, causing infinite update loops or restoring empty values immediately after resets.

Resolution: Only emit onChange callbacks in response to manual user actions (like clicks or keystrokes), never in response to programmatic framework calls like writeValue.

Interviewer Note: 💡 AI models often implement ControlValueAccessor by linking writeValue directly to state changes that fire onChange, leading to bugs when forms are reset.

Anti-AI Scenario #4: Why does updating a signal inside a template function (e.g., inside {{ updateSignal() }}) cause an infinite loop in some zones but not in Zoneless mode?

Level: Anti-AI Validation | Category: Signals | Time: 12 mins

Expected Answer

Updating a signal inside a template function causes state changes during rendering:

  • Zone.js Mode: The template updates a signal, which flags the component as dirty and schedules a change detection run. This triggers another render, calling the function and updating the signal again in an infinite loop.
  • Zoneless Mode: In Zoneless Angular, change detection is decoupled from Zone.js loops. While updating a signal flags the component as dirty, Angular blocks additional re-runs if it's already in the middle of a rendering cycle, throwing a dev warning instead of freezing the browser.
Interviewer Note: 💡 Tests change detection timing. Strong candidates will explain how Zoneless mode protects against UI freeze cascades.

Anti-AI Scenario #5: Why does a router-level resolver that returns NEVER block navigation completely without any error log, and how can we safeguard against it?

Level: Anti-AI Validation | Category: Routing | Time: 11 mins

Expected Answer

The Angular Router subscribes to resolver observables and waits for them to complete before completing the route transition. The NEVER observable emits no values and never completes. As a result, the router's transition remains pending indefinitely without throwing errors, leaving the user with a frozen screen.

Safeguard: Always pipe resolvers through completion operators like take(1) or timeout() to guarantee they resolve and complete within a set timeframe.

Interviewer Note: 💡 Check if the candidate knows how router subscriptions handle observable completion.

Anti-AI Scenario #6: Why do RxJS streams wrapped in takeUntilDestroyed leak memory if they are subscribed inside a dynamic factory function?

Level: Anti-AI Validation | Category: RxJS | Time: 12 mins

Expected Answer

takeUntilDestroyed() searches for the active injection context to find a DestroyRef reference. If called inside a dynamic factory function that executes asynchronously outside of application bootstrap phases, the active injection context is lost. If no context is found, takeUntilDestroyed throws an error or fails silently, leaving subscriptions active and leaking memory.

Resolution: Capture DestroyRef explicitly during class initialization and pass it as an argument: takeUntilDestroyed({ destroyRef }).

Interviewer Note: 💡 Excellent test for injection context boundaries. AI models often suggest takeUntilDestroyed() for all contexts, ignoring initialization requirements.

Anti-AI Scenario #7: Why does using @defer with 'on viewport' sometimes fail to load when combined with default CSS layout shifting?

Level: Anti-AI Validation | Category: Performance Optimization | Time: 12 mins

Expected Answer

The on viewport trigger uses the Intersection Observer API under the hood, watching the @placeholder block's visibility.

  • The Problem: If the placeholder block has no defined height (e.g. height: 0px), it renders offscreen but shifts into the viewport during layout rendering. This triggers immediate loading of the deferred component even before user scrolls to it, defeating the purpose of lazy-loading.
  • Resolution: Always set a minimum height on the placeholder block (e.g. min-height: 200px) to preserve space and ensure Intersection Observer calculations are accurate.
Interviewer Note: 💡 This question verifies if the candidate has practical experience with dynamic layout rendering, where CSS styles directly affect Angular's logical triggers.

Anti-AI Scenario #8: Why does an HttpClient interceptor that clones the Request body and adds headers break multipart file uploads, and how do we resolve it?

Level: Anti-AI Validation | Category: HttpClient | Time: 12 mins

Expected Answer

When uploading files using FormData, the browser automatically calculates and appends the Content-Type: multipart/form-data; boundary=----WebKitFormBoundary... header. If an interceptor intercepting requests clones the body and sets a static Content-Type: multipart/form-data header, the boundary token is lost. The server will fail to parse the upload payload and return a 400 Bad Request error.

Resolution: In interceptors, never set the Content-Type header manually if the request body is an instance of FormData. Let the browser handle boundary assignments automatically.

Interviewer Note: 💡 A common production bug. AI models often suggest adding Content-Type headers globally in interceptors, which breaks multipart uploads.

Anti-AI Scenario #9: Why is effect() not guaranteed to run if it is defined inside a component method called dynamically rather than the constructor?

Level: Anti-AI Validation | Category: Signals | Time: 12 mins

Expected Answer

An effect() registers its dependency watchers upon execution inside an active injection context. If defined inside a dynamically called method (like a button click handler), the initialization phase has already finished. Even if you pass an injector reference using runInInjectionContext(injector, () => { effect(...) }), the effect may fail to register cleanups correctly or trigger twice if the method runs multiple times, leading to memory leaks and unpredictable behavior.

Best Practice: Always define effects statically within constructor scopes or class field initialization phases. Do not instantiate them dynamically inside component methods.

Interviewer Note: 💡 Deep signal execution internals. Ensures the candidate understands that effects are design-time decorators rather than run-time event listeners.

Anti-AI Scenario #10: Why does @HostListener('window:scroll') degrade page scrolling performance even with OnPush change detection, and how do you resolve it?

Level: Anti-AI Validation | Category: Performance Optimization | Time: 11 mins

Expected Answer

In Zone.js applications, @HostListener('window:scroll') forces Angular to trigger change detection on the entire component tree every time the user scrolls a single pixel. Even if the component uses OnPush change detection, the scroll event is intercepted by Zone.js, triggering layout checks and causing lag during scroll animations.

Resolution: Run the listener outside the zone using NgZone.runOutsideAngular(), or migrate the application to Zoneless Angular where event listeners do not trigger global change detection loops automatically.

Interviewer Note: 💡 Check if the candidate knows how Zone.js intercepts browser events and how to run code outside the zone.

Anti-AI Scenario #11: Why does using a custom TrackByFunction that returns index instead of unique ID fail to improve performance for list re-ordering in Angular?

Level: Anti-AI Validation | Category: Templates | Time: 11 mins

Expected Answer

The purpose of trackBy is to help Angular identify which items in a list have been added, removed, or re-ordered. If your trackBy function returns the item's index (which is Angular's default behavior), the index of a re-ordered item remains the same. Angular will reuse the existing DOM elements and simply update their inputs, causing unnecessary component re-renders and potentially leaving stale state in child components.

Resolution: Always return a unique identifier (like an id property) from trackBy functions so Angular can track the elements correctly.

Interviewer Note: 💡 Tests lists rendering internals. Candidates must explain why trackBy index is redundant and fails to optimize list re-ordering.

Anti-AI Scenario #12: Why does a child component's @ContentChildren query return empty when the projected content is wrapped in a <ng-template> rather than directly projected?

Level: Anti-AI Validation | Category: Components | Time: 12 mins

Expected Answer

@ContentChildren queries elements that are instantiated and projected directly into the component's content. When content is wrapped in a <ng-template>, the template is not instantiated immediately; it is treated as a blueprint. Because the child elements do not exist in the DOM yet, the content query returns empty.

Resolution: Query the TemplateRef references instead, or instantiate the template dynamically using createEmbeddedView before querying its contents.

Interviewer Note: 💡 Advanced template projection. Candidates should explain the difference between eager content projection and lazy template rendering.

Anti-AI Scenario #13: Why does adding provideHttpClient(withInterceptors([...])) in a lazy-loaded route configuration instead of app.config.ts isolate interceptors, and how does it bypass global interceptors?

Level: Anti-AI Validation | Category: HttpClient | Time: 12 mins

Expected Answer

Adding provideHttpClient() to a route configuration creates a new instance of HttpClient scoped to that route's EnvironmentInjector. Components inside this route will resolve this scoped HttpClient rather than the global one from app.config.ts. As a result, HTTP requests sent from these components will only pass through interceptors registered in the route's provider list, completely bypassing global interceptors like auth tokens or error loggers.

Best Practice: Register provideHttpClient() only once in the root app.config.ts file. Avoid duplicating provider registrations in lazy-loaded routes.

Interviewer Note: 💡 Tests hierarchical injector knowledge. Strong candidates explain why duplicate provider declarations create isolated service instances.

Anti-AI Scenario #14: Why does defining a signal-based computed property that references a mutable object array fail to trigger updates when a property of an array element is modified?

Level: Anti-AI Validation | Category: Signals | Time: 12 mins

Expected Answer

Angular Signals check for updates using object reference equality (===). If you modify a property of an object inside an array (e.g. users()[0].name = 'John'), the array reference itself does not change. Because the reference is the same, the signal does not notify its consumers, and computed properties will not re-evaluate.

Resolution: Update signal state immutably by creating a new array reference containing copies of the updated objects:


this.users.update(list => 
  list.map((u, i) => i === 0 ? { ...u, name: 'John' } : u)
);
Interviewer Note: 💡 Tests immutability requirements in reactive frameworks. AI models often suggest direct mutations, which breaks computed signal triggers.

Frequently Asked Questions

Are these Angular interview questions suitable for experienced developers?

Yes. The guide covers junior, mid-level and senior topics.

Do these questions include Angular Signals?

Yes. Modern Angular concepts such as Signals, SSR and Hydration are included.

Are practical coding exercises included?

Yes. Practical and architecture rounds are covered.

Is this suitable for experienced developers?

Yes, it covers junior through architect level topics.

Does it include practical questions?

Yes, practical, debugging and architecture rounds are included.