Advanced Angular Interview Questions and Answers

๐Ÿ“‹ Table of Contents โ–พ
  1. Questions & Answers
  2. 📝 Knowledge Check

▲ Advanced Angular Interview Questions

This lesson targets mid-to-senior level roles. Topics include RxJS operators, reactive forms, OnPush change detection, lazy loading, guards, resolvers, ViewChild, NgRx, interceptors, and SSR. These questions separate developers who use Angular from those who truly understand it.

Questions & Answers

01 What are Reactive Forms? How do you use FormBuilder?

Forms Reactive Forms manage form state entirely in the component class using FormControl, FormGroup, and FormArray. They are explicit, immutable, and easy to test.

import { FormBuilder, Validators } from '@angular/forms';

@Component({ ... })
export class RegisterComponent {
  form = this.fb.group({
    name:     ['', [Validators.required, Validators.minLength(2)]],
    email:    ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
    address:  this.fb.group({           // nested group
      street: [''],
      city:   ['', Validators.required]
    })
  });

  constructor(private fb: FormBuilder) {}

  submit() {
    if (this.form.valid) console.log(this.form.value);
  }

  get emailCtrl() { return this.form.get('email'); }
}
<form [formGroup]="form" (ngSubmit)="submit()">
  <input formControlName="email">
  <div *ngIf="emailCtrl?.invalid && emailCtrl?.touched">
    <span *ngIf="emailCtrl?.errors?.['required']">Email is required.</span>
    <span *ngIf="emailCtrl?.errors?.['email']">Invalid format.</span>
  </div>
  <button type="submit" [disabled]="form.invalid">Register</button>
</form>

02 What is RxJS and why is Angular built around it?

RxJS RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using Observables. Angular uses RxJS throughout: HttpClient, EventEmitter, Router events, FormControl.valueChanges, and AsyncPipe all return Observables.

Why RxJS fits Angular:

  • Many Angular operations are inherently asynchronous (HTTP, user events, routing) โ€” Observables model these as streams
  • Operators like switchMap, debounceTime, distinctUntilChanged eliminate complex async logic
  • Observables are lazy โ€” nothing executes until subscribed
  • Cancellation is built in โ€” unsubscribing cancels pending HTTP requests
// Typeahead search โ€” RxJS makes this elegant
this.searchCtrl.valueChanges.pipe(
  debounceTime(300),           // wait 300ms after last keystroke
  distinctUntilChanged(),      // only if value changed
  filter(q => q.length > 2),  // only if 3+ chars
  switchMap(q => this.api.search(q))  // cancel previous request
).subscribe(results => this.results = results);

03 What are the higher-order RxJS mapping operators? When do you use each?

RxJS These operators map each outer Observable emission to an inner Observable, differing in how they handle multiple concurrent inner streams:

  • switchMap โ€” cancels the previous inner Observable when a new outer value arrives. Use for search (only care about the latest query), navigation.
  • mergeMap โ€” runs all inner Observables concurrently. Use when order doesn’t matter and all results are needed (fire-and-forget uploads).
  • concatMap โ€” queues inner Observables and runs them one at a time in order. Use when order matters (sequential API calls, save operations).
  • exhaustMap โ€” ignores new outer values while an inner Observable is running. Use for login button (ignore extra clicks while request is in flight).
// switchMap โ€” cancel previous search on new keystroke
searchInput.valueChanges.pipe(
  switchMap(q => this.api.search(q))
);

// exhaustMap โ€” ignore extra login button clicks
loginBtn.clicks.pipe(
  exhaustMap(() => this.authService.login(credentials))
);

04 What is ChangeDetectionStrategy.OnPush? How does it improve performance?

Performance By default, Angular’s change detection checks every component in the tree on every event. OnPush tells Angular to only check a component when:

  • An @Input() reference changes (not deep mutation)
  • An event originates from within this component or its children
  • An Observable subscribed via async pipe emits a new value
  • ChangeDetectorRef.markForCheck() is called manually
@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush  // opt-in
})
export class ProductListComponent {
  @Input() products: Product[] = [];
  // Angular skips this component unless products reference changes
}

Critical rule: With OnPush, mutating an array or object will NOT trigger an update โ€” you must replace the reference: this.products = [...this.products, newItem]. This is why OnPush pairs naturally with immutable data patterns and the async pipe.

05 What are Route Guards? Explain CanActivate and CanDeactivate.

Routing Route guards control whether a user can navigate to or away from a route.

// CanActivate โ€” protect a route (e.g., auth check)
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot): boolean | UrlTree {
    if (this.auth.isLoggedIn()) return true;
    return this.router.createUrlTree(['/login']); // redirect instead of just blocking
  }
}

// Apply to a route
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
// CanDeactivate โ€” prevent navigation away (e.g., unsaved form)
export class UnsavedChangesGuard implements CanDeactivate<FormComponent> {
  canDeactivate(component: FormComponent): boolean {
    if (component.form.dirty) {
      return confirm('You have unsaved changes. Leave anyway?');
    }
    return true;
  }
}

Other guards: CanLoad (prevent lazy module download), CanActivateChild (protect child routes), Resolve (pre-fetch data before component loads).

06 What is Lazy Loading in Angular? How do you configure it?

Performance Lazy loading defers downloading a feature module’s JavaScript until the user navigates to that feature’s route. This significantly reduces the initial bundle size and improves startup time.

// app-routing.module.ts โ€” lazy load AdminModule
const routes: Routes = [
  { path: 'home', component: HomeComponent },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
    // AdminModule and all its components are in a SEPARATE bundle
    // Only downloaded when user visits /admin
  }
];

// admin-routing.module.ts โ€” routes within the lazy module
const adminRoutes: Routes = [
  { path: '',       component: AdminDashboardComponent },
  { path: 'users',  component: UserManagementComponent }
];

Preloading strategies: All lazy modules are downloaded after initial load using PreloadAllModules strategy โ€” first load is fast, subsequent navigation is instant:

RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })

07 What is a Resolver in Angular routing?

Routing A Resolver pre-fetches data before a route is activated. The component receives data that is already available when it initialises โ€” no loading state required in the component.

@Injectable({ providedIn: 'root' })
export class ProductResolver implements Resolve<Product> {
  constructor(private productService: ProductService) {}

  resolve(route: ActivatedRouteSnapshot): Observable<Product> {
    const id = Number(route.paramMap.get('id'));
    return this.productService.getProduct(id);
  }
}

// Route configuration
{ path: 'products/:id', component: ProductDetailComponent, resolve: { product: ProductResolver } }

// Component โ€” data is ready immediately in ngOnInit
ngOnInit() {
  this.product = this.route.snapshot.data['product'];
}

Resolvers are useful for detail pages where you must have data before rendering. However, they block navigation until the Observable completes โ€” consider skeleton screens or *ngIf with a loading spinner as a lighter alternative for slow APIs.

08 What is ViewChild and ContentChild? When do you use each?

DOM Access

  • @ViewChild โ€” queries elements or child components defined in the component’s own template. Available after ngAfterViewInit.
  • @ContentChild โ€” queries elements projected into the component via <ng-content>. Available after ngAfterContentInit.
// @ViewChild โ€” access a template ref or child component
@Component({ template: `
  <input #searchInput type="text">
  <app-chart #chartRef></app-chart>
`})
export class SearchComponent implements AfterViewInit {
  @ViewChild('searchInput') input!: ElementRef;
  @ViewChild('chartRef') chart!: ChartComponent;

  ngAfterViewInit() {
    this.input.nativeElement.focus();  // focus the input on load
    this.chart.render(this.data);       // call child component method
  }
}

// @ContentChild โ€” access projected content
export class CardComponent implements AfterContentInit {
  @ContentChild(CardHeaderComponent) header!: CardHeaderComponent;
  ngAfterContentInit() { console.log(this.header); }
}

09 What is an HttpInterceptor? Give a real-world example.

HTTP An HttpInterceptor sits in the HTTP pipeline and can inspect, transform, or handle HTTP requests and responses globally โ€” without touching each service individually.

// Auth interceptor โ€” adds JWT token to every request
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.auth.getToken();

    const cloned = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${token}`)
    });

    return next.handle(cloned).pipe(
      catchError(err => {
        if (err.status === 401) this.auth.logout();  // handle 401 globally
        return throwError(() => err);
      })
    );
  }
}

// Register in AppModule providers
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }

Other uses: logging, loading spinners (increment/decrement a counter on request/response), caching, error toast notifications, request retry logic.

10 What is NgRx? Explain its core concepts.

State Management NgRx is Angular’s Redux-inspired state management library built on RxJS. It implements a unidirectional data flow using four core concepts:

  • Store โ€” a single immutable state tree observable via store.select()
  • Actions โ€” plain objects describing an event: { type: '[User] Load Users' }
  • Reducers โ€” pure functions (state, action) => newState that produce the next state
  • Selectors โ€” memoized functions to derive and slice state
  • Effects โ€” handle side effects (HTTP calls) in response to actions, then dispatch new actions
// Action
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction('[User] Load Users Success', props<{ users: User[] }>());

// Effect
loadUsers$ = createEffect(() => this.actions$.pipe(
  ofType(loadUsers),
  switchMap(() => this.userService.getUsers().pipe(
    map(users => loadUsersSuccess({ users }))
  ))
));

NgRx suits large teams and apps with complex shared state. For simpler apps, a service with BehaviorSubject or a standalone state library like NgXs or Akita is often sufficient.

11 What is the difference between Subject, BehaviorSubject, and ReplaySubject?

RxJS All three are both Observable (can subscribe to) and Observer (can call .next()) โ€” they are multicast, sharing a single execution with all subscribers.

  • Subject โ€” no initial value, no buffer. New subscribers only get values emitted after they subscribe. Use for events (button clicks, route events).
  • BehaviorSubject โ€” requires an initial value. New subscribers immediately receive the latest value. Perfect for shared state (current user, selected theme, cart count).
  • ReplaySubject(n) โ€” buffers the last n values. New subscribers receive the buffered history. Use when late subscribers need past values (activity log, cached HTTP responses).
const subject = new Subject<number>();
subject.subscribe(v => console.log('A:', v));  // A sees 2,3
subject.next(1); // nobody else subscribed yet โ€” lost
subject.next(2); // A: 2
const bs = new BehaviorSubject<number>(0);
bs.subscribe(v => console.log('B:', v));  // immediately: B: 0
bs.next(1); // B: 1
bs.subscribe(v => console.log('C:', v));  // immediately: C: 1 (last value)

12 What is the difference between SharedModule and CoreModule?

Architecture These are an Angular architecture convention for organising code:

CoreModule:

  • Contains singleton services and application-wide providers (AuthService, LoggingService, HttpInterceptors)
  • Imported ONCE in AppModule only
  • Typically contains the app shell: NavbarComponent, FooterComponent, SidebarComponent
  • Often includes a guard against double imports

SharedModule:

  • Contains reusable components, directives, and pipes used across multiple feature modules
  • Declares AND exports common things: ButtonComponent, SpinnerComponent, TruncatePipe
  • Re-exports CommonModule, FormsModule to avoid importing them in every feature module
  • Imported in every feature module that needs the shared items
  • Should NOT provide services (would create multiple instances)
13 What is the difference between ngOnChanges and ngDoCheck?

Lifecycle

  • ngOnChanges โ€” fires when a bound @Input() property changes its reference. Angular tracks by reference, not by value. If you pass the same object reference but mutate its properties, ngOnChanges does NOT fire. Receives a SimpleChanges map with previous and current values.
  • ngDoCheck โ€” fires on every change detection cycle regardless of whether inputs changed. Allows you to implement custom change detection logic โ€” for example, detecting mutations inside an array or object that Angular wouldn’t normally detect.
ngOnChanges(changes: SimpleChanges) {
  if (changes['user']) {
    const prev = changes['user'].previousValue;
    const curr = changes['user'].currentValue;
    console.log('User changed from', prev?.name, 'to', curr?.name);
  }
}

ngDoCheck() {
  // Runs very frequently โ€” keep logic minimal to avoid performance issues
  if (this.items.length !== this.lastCount) {
    this.lastCount = this.items.length;
    this.detectItemChanges();
  }
}

14 What is Angular Universal (Server-Side Rendering)?

SSR Angular Universal enables server-side rendering โ€” the Angular app runs on a Node.js server, produces full HTML for each request, and sends it to the browser for fast initial display and SEO. The browser then hydrates the app to make it interactive.

# Add SSR to an existing Angular project
ng add @angular/ssr

# Builds both browser and server bundles
ng build
node dist/my-app/server/server.mjs

Benefits: Faster First Contentful Paint (FCP), full HTML for search engine crawlers, social media link previews (OG tags), better Core Web Vitals.

SSR gotchas:

  • Browser-specific APIs (window, document, localStorage) are not available on the server โ€” guard with isPlatformBrowser()
  • HTTP requests made during SSR run server-side โ€” use TransferState to pass data to the client and avoid duplicate requests
  • Angular 17+ has improved SSR with partial hydration support
15 What is a custom Directive? Write a practical example.

Directives Custom directives let you add reusable behaviour to any HTML element.

// Highlight directive โ€” changes background on hover
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  @Input('appHighlight') highlightColor = 'yellow';

  constructor(private el: ElementRef, private renderer: Renderer2) {}

  @HostListener('mouseenter') onEnter() {
    this.renderer.setStyle(this.el.nativeElement, 'background', this.highlightColor);
  }

  @HostListener('mouseleave') onLeave() {
    this.renderer.removeStyle(this.el.nativeElement, 'background');
  }
}

// Usage
<p appHighlight="lightblue">Hover over me!</p>

Use Renderer2 (not direct DOM manipulation) for SSR compatibility and security. @HostBinding binds a property of the host element (@HostBinding('class.active') isActive = true), while @HostListener listens for host element events.

16 What is a custom Pipe? Write a practical example.

Pipes Custom pipes transform template values just like built-in pipes.

// Truncate pipe โ€” cuts text to a max length
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit = 100, ellipsis = '...'): string {
    if (!value) return '';
    return value.length > limit ? value.substring(0, limit) + ellipsis : value;
  }
}

// Usage in template
<p>{{ description | truncate:150 }}</p>
<p>{{ description | truncate:50:'โ€” read more' }}</p>

Pure vs Impure pipes:

  • Pure (default) โ€” only runs when the input value reference changes. Highly efficient. Suitable for most transformations.
  • Impure โ€” runs on every change detection cycle. Use sparingly (for filtering arrays where the array reference doesn’t change, or translating dynamic strings). Mark with @Pipe({ name: '...', pure: false }).
17 What is forRoot() and forChild() in Angular modules?

Architecture forRoot() and forChild() are static methods on Angular modules that ensure services are only registered once (as singletons) even when a module is imported multiple times.

// RouterModule example
// In AppModule โ€” registers the Router singleton + routes
RouterModule.forRoot(appRoutes, { preloadingStrategy: PreloadAllModules })

// In feature/lazy module โ€” adds routes WITHOUT re-registering the Router singleton
RouterModule.forChild(featureRoutes)

Why this pattern exists:

  • forRoot() returns the module with providers, creating singleton services
  • forChild() returns the module WITHOUT providers โ€” only declarations/directives/pipes
  • If a lazy-loaded module imported forRoot(), it would create a second router instance, breaking navigation

Use this pattern in your own modules that provide singleton services but are imported in multiple places.

18 How do you handle errors globally in an Angular application?

Error Handling Angular provides several layers for error handling:

1. Global ErrorHandler:

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  constructor(private logger: LoggingService) {}
  handleError(error: Error) {
    this.logger.logError(error);
    // Show user-friendly notification
  }
}
// Register: { provide: ErrorHandler, useClass: GlobalErrorHandler }

2. HTTP errors via Interceptor:

return next.handle(req).pipe(
  catchError((error: HttpErrorResponse) => {
    if (error.status === 403) this.router.navigate(['/forbidden']);
    if (error.status === 500) this.toast.error('Server error. Please try again.');
    return throwError(() => error);
  })
);

3. Observable-level with catchError:

this.userService.getUsers().pipe(
  catchError(err => { this.error = err.message; return of([]); })
).subscribe(users => this.users = users);

19 What are environment variables in Angular and how do you use them?

Tooling Angular uses environment files to manage configuration per build target. The CLI automatically swaps the correct file during build.

// src/environments/environment.ts (development)
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
  featureFlags: { darkMode: true }
};

// src/environments/environment.prod.ts (production)
export const environment = {
  production: true,
  apiUrl: 'https://api.myapp.com',
  featureFlags: { darkMode: false }
};

// angular.json โ€” file replacement configuration
"fileReplacements": [{
  "replace": "src/environments/environment.ts",
  "with": "src/environments/environment.prod.ts"
}]

// Usage in a service
import { environment } from '../environments/environment';

@Injectable({ providedIn: 'root' })
export class ApiService {
  private baseUrl = environment.apiUrl; // correct URL per build
}

20 What is Angular Animation? Give a basic example.

Animation Angular’s animation module (@angular/animations) is built on the Web Animations API and integrates with change detection for declarative, state-driven animations.

import { trigger, state, style, animate, transition } from '@angular/animations';

@Component({
  selector: 'app-panel',
  template: `<div [@panelState]="isOpen ? 'open' : 'closed'">Content</div>`,
  animations: [
    trigger('panelState', [
      state('open',   style({ height: '*',   opacity: 1 })),
      state('closed', style({ height: '0px', opacity: 0 })),
      transition('open <=> closed', [animate('300ms ease-in-out')])
    ])
  ]
})
export class PanelComponent { isOpen = false; }

Common use cases: route transition animations, list enter/leave animations (query + stagger), modal fade-in/out. Add BrowserAnimationsModule (or NoopAnimationsModule for tests) to AppModule imports.

21 How do you optimise the performance of an Angular application?

Performance

  • OnPush change detection โ€” drastically reduces checks in large component trees
  • Lazy loading โ€” split routes into separate bundles; only load what’s needed
  • trackBy in *ngFor โ€” reuse DOM nodes instead of recreating the entire list
  • Async pipe โ€” auto-unsubscribes; works with OnPush
  • Pure pipes over methods in templates โ€” methods in templates recalculate on every CD cycle; pure pipes are memoized
  • Virtual scrolling โ€” CdkVirtualScrollViewport from Angular CDK renders only visible items in large lists
  • Preloading strategy โ€” PreloadAllModules or custom strategy for near-instant navigation after first load
  • Image optimisation โ€” use NgOptimizedImage directive (@angular/common) for lazy loading, priority hints, and LCP improvements
  • Bundle analysis โ€” run ng build --stats-json then use webpack-bundle-analyzer to find large dependencies

📝 Knowledge Check

Test your understanding of advanced Angular patterns and techniques.

🧠 Quiz Question 1 of 5

Which RxJS operator cancels the previous inner Observable when a new outer value arrives โ€” ideal for search typeahead?





🧠 Quiz Question 2 of 5

What does ChangeDetectionStrategy.OnPush tell the Angular change detector?





🧠 Quiz Question 3 of 5

What is the primary purpose of an HttpInterceptor in Angular?





🧠 Quiz Question 4 of 5

What does Lazy Loading achieve in Angular routing?





🧠 Quiz Question 5 of 5

Which decorator gives you access to a child component or DOM element declared in the component’s own template?