interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools

Angular Interview Questions and Answers

103 hand-picked Angular interview questions with detailed answers. Open the interactive version above to search, filter by difficulty, run code, bookmark questions and track your progress.

What is Angular and how is it different from AngularJS?

Angular (v2+) is a TypeScript-based, component-driven SPA framework by Google. It's opinionated and batteries-included: components, dependency injection, routing, forms, HttpClient, RxJS and a powerful CLI all ship in the box.

AngularJS (v1.x) is a different, older framework — JavaScript-based, using controllers, $scope, and the digest cycle. Angular 2+ is a complete rewrite: component architecture, a hierarchical injector, AOT compilation, and far better performance. Say "Angular" for 2+, "AngularJS" only for 1.x.

Lifecycle hooks — order and what each is for.

Order: constructorngOnChangesngOnInitngDoCheckngAfterContentInitngAfterContentCheckedngAfterViewInitngAfterViewCheckedngOnDestroy.

  • ngOnChanges — runs on every @Input change with a SimpleChanges object.
  • ngOnInit — one-time setup once inputs are ready; fetch data here, not in the constructor.
  • ngAfterViewInit — the view and @ViewChild refs are available.
  • ngOnDestroy — cleanup: unsubscribe, clear timers, detach listeners.

What are the types of data binding?

Four kinds, distinguished by direction of data flow:

  • Interpolation{{ value }} (component → view, text).
  • Property binding[src]="url" (component → view, a DOM property).
  • Event binding(click)="fn()" (view → component).
  • Two-way binding[(ngModel)]="name" (both ways; the "banana in a box" is just [x] + (xChange)).

What is the Change Detection (CD) Tree in Angular?

The Change Detection (CD) tree mirrors your component hierarchy — every component view is a node. Each CD cycle, Angular walks the tree top-down from the root, re-evaluates each component's template bindings, and updates the DOM where a value changed.

It's triggered by events, HTTP, and timers (via zone.js). A component using the OnPush strategy lets Angular skip its entire subtree unless an input reference changes, an event fires inside it, or it's explicitly marked — which is the main lever for change-detection performance.

AppComponent
├── HeaderComponent
├── DashboardComponent
│   ├── UserCardComponent
│   └── ChartComponent
└── FooterComponent

What are the types of directives?

Three kinds:

  • Components — a directive with a template (the most common).
  • Structural — change DOM layout by adding/removing elements: *ngIf, *ngFor, *ngSwitch (v17+: @if, @for).
  • Attribute — change appearance/behaviour of an existing element: ngClass, ngStyle, or your own.

Only one structural directive is allowed per element.

How do components communicate with each other?

Pick the channel by the relationship between the components:

  • Parent → child@Input().
  • Child → parent@Output() with an EventEmitter.
  • Unrelated components — a shared service exposing a BehaviorSubject/signal (or an NgRx store).
  • Parent reaching into a child@ViewChild.

Prefer a shared service over long @Input/@Output chains.

@Output() saved = new EventEmitter<Item>();
onSave() { this.saved.emit(this.item); }

Explain Dependency Injection (DI) and providedIn: 'root' in Angular.

Dependency Injection (DI) is a pattern where Angular creates and supplies the objects a class needs, instead of the class doing new itself. Angular resolves these through a hierarchical injector, which keeps code loosely coupled and easy to test (swap a real service for a mock).

@Injectable({ providedIn: 'root' }) registers a service in the root injector as an app-wide singleton, and it's tree-shakable (dropped from the bundle if unused). Provide a service at component level only when you want a fresh, private instance.

@Injectable({
  providedIn: 'root'
})
export class UserService {
  users = ['John', 'Alice'];
}

@Component({...})
export class HomeComponent {
  constructor(private userService: UserService) {}
}

Template-driven vs Reactive forms — which and why?

Two approaches:

  • Template-driven — the form model is built implicitly in the template via ngModel. Quick and readable for small forms, but harder to test and scale.
  • Reactive — the model is defined explicitly in the TS class (FormGroup, FormControl, FormBuilder). Synchronous, strongly typed, testable, and better for dynamic/complex validation.

Rule of thumb: any real, validated, or dynamic form → Reactive.

form = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
});

Explain routing: lazy loading, guards, and resolvers.

  • Lazy loadingloadChildren / loadComponent loads a feature only on first visit, shrinking the initial bundle and speeding up first paint.
  • Guards — allow or block navigation: CanActivate (can I enter?), CanDeactivate (can I leave? — unsaved-changes prompt), CanMatch (should this route even match?).
  • Resolvers — pre-fetch data before the route activates, so the component opens with its data ready instead of flashing empty.
{ path: 'admin', loadComponent: () =>
  import('./admin.component').then(m => m.AdminComponent) }

What are HTTP interceptors used for?

HTTP interceptors are middleware that sit between HttpClient and the server, seeing every outgoing request and incoming response. They let you handle cross-cutting concerns in one place instead of in every service.

Common uses: attach auth/JWT headers, add a correlation id, show a global loader, log, retry transient failures, handle errors centrally, and refresh expired tokens. They're chainable and run in registration order.

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('token');

    const clonedReq = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });

    return next.handle(clonedReq);
  }
}

Explain content projection (ng-content) and ViewChild/ContentChild.

Content projection with <ng-content> lets a component render markup the parent placed between its tags — like slots — which is the key to reusable wrappers such as cards, modals and layouts.

@ViewChild queries an element/component from your own template (ready in ngAfterViewInit); @ContentChild queries projected content that came from the parent (ready in ngAfterContentInit). Memory hook: View = mine, Content = projected.

How does change detection work? Default vs OnPush.

zone.js patches async APIs (events, timers, XHR) and tells Angular to re-check the component tree afterward. The Default strategy checks every component each cycle — safe, but can be wasteful.

OnPush re-checks a component only when: an @Input reference changes, an event fires inside it, an async pipe emits, or you call markForCheck(). Combined with immutable data it drastically cuts re-renders. (Signals push Angular toward zone-less CD.)

@Component({ changeDetection: ChangeDetectionStrategy.OnPush })

List concrete performance optimisations in Angular.

  • ChangeDetectionStrategy.OnPush with immutable inputs.
  • trackBy in *ngFor (or track in @for) to reuse DOM rows.
  • Lazy-load feature routes and use @defer for heavy below-the-fold blocks.
  • Prefer the async pipe over manual subscriptions.
  • Avoid function calls in templates (they re-run every cycle); use pure pipes instead.
  • Virtual scrolling (cdk-virtual-scroll) for long lists.

Pure vs impure pipes — and what is a pipe?

A pipe transforms a value for display in the template ({{ date | date:'short' }}) without mutating the source.

Pure (the default) recomputes only when the input reference changes — cheap and cacheable. Impure runs on every change-detection cycle (e.g. the async pipe, or a filter over a mutating array) — powerful but costly, so use it sparingly. Never do HTTP or side effects in a pipe.

@Pipe({ name: 'myFilter', pure: false })

What are Signals? How do they differ from RxJS?

Signals (v16+) are a fine-grained reactivity primitive: a wrapper around a value that notifies its consumers when it changes, enabling precise, potentially zone-less change detection. You read one by calling it — count() — and derive with computed()/effect().

vs RxJS: signals are synchronous and always hold a current value — ideal for local component/UI state. RxJS models asynchronous streams over time (HTTP, events). Use both, and bridge with toSignal() / toObservable().

count = signal(0);
double = computed(() => this.count() * 2);
effect(() => console.log(this.count()));
this.count.set(1); // or .update(v => v + 1)

What are Standalone Components and the new Angular Control Flow?

Standalone components don't need an NgModule; they declare their own dependencies in the component's imports array. This removes NgModule boilerplate, keeps deps explicit, and is the modern default (v17+).

The new built-in control flow@if, @for, @switch, plus @let and @defer — replaces the structural directives with faster, type-checked, more readable syntax. Note that @for requires a track expression.

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [CommonModule],
  template: `
    @if (users.length) {
      <ul>
        @for (user of users; track user.id) {
          <li>{{ user.name }}</li>
        }
      </ul>
    } @else {
      <p>No users found.</p>
    }
  `
})
export class HomeComponent {}

What is View Encapsulation?

View encapsulation controls how a component's styles are scoped:

  • Emulated (default) — Angular rewrites selectors with generated attributes so styles stay local; no real Shadow DOM.
  • ShadowDom — uses the browser's native Shadow DOM for true isolation.
  • None — styles become global and leak everywhere.

Avoid ::ng-deep (deprecated) to pierce a child's styles.

@Component({ encapsulation: ViewEncapsulation.Emulated })

AOT vs JIT compilation.

AOT (Ahead-of-Time) compiles templates at build time: smaller bundles, faster startup, template errors caught early, and no compiler shipped to the browser. It's the default for production (and dev in modern Angular).

JIT (Just-in-Time) compiles templates in the browser at runtime — slower and larger, historically used only for quick debugging. The underlying compiler/renderer is Ivy.

What does the static flag on @ViewChild do?

The static flag controls when a @ViewChild query resolves:

  • { static: true } — resolved before the first change detection, so it's available in ngOnInit. Only use it for elements that are always present (not inside *ngIf/*ngFor).
  • { static: false } (default) — resolved after the view initialises, available in ngAfterViewInit.

An undefined query is usually a timing issue — read too early, or the element is conditionally hidden.

@ViewChild('chart', { static: false }) chart!: ElementRef;

What are @HostListener and @HostBinding?

Both decorators target the directive/component's host element:

  • @HostListener('click', ['$event']) — subscribe to a DOM event on the host and run a method.
  • @HostBinding('class.active') — bind a host class, style, attribute or property to a class field.

They're the clean, SSR-safe way to build attribute directives without touching the DOM directly.

@HostBinding('class.open') isOpen = false;

@HostListener('click')
toggle() {
  this.isOpen = !this.isOpen;
}

Difference between ng-template, ng-container, and ng-content?

  • ng-template — defines an inert template that is not rendered until Angular instantiates it (used by *ngIf/else and structural directives).
  • ng-container — a logical grouping element that adds no extra DOM node — handy for applying a structural directive without a wrapper.
  • ng-content — projects markup passed in by a parent component (content projection).
<ng-container *ngIf="isLoggedIn">
  <h2>Welcome</h2>
</ng-container>

<ng-template #loading>
  Loading...
</ng-template>

<!-- Child Component -->
<ng-content></ng-content>

Why is the async pipe preferred over manual subscription?

The async pipe subscribes to an Observable/Promise in the template, renders the latest value, and automatically unsubscribes when the component is destroyed — eliminating a whole class of memory leaks and manual ngOnDestroy plumbing.

It also pairs naturally with OnPush: each emission marks the component for check. Prefer it over subscribing in the component class.

<div *ngIf="user$ | async as user">{{ user.name }}</div>

Why use Renderer2 instead of direct DOM access?

Renderer2 is a platform-agnostic API for DOM work (setStyle, addClass, listen). Because it abstracts the DOM, your code keeps working where there's no real DOM — server-side rendering and web workers — and it respects Angular's security model.

Avoid touching document or nativeElement.innerHTML directly: that breaks SSR and opens XSS holes.

constructor(private r: Renderer2, private el: ElementRef) {}
ngOnInit() { this.r.addClass(this.el.nativeElement, 'active'); }

What is an NgModule and what are its main metadata fields?

An @NgModule groups related Angular code into a cohesive block. Key metadata:

  • declarations — components, directives and pipes this module owns.
  • imports — other modules whose exports it needs.
  • exports — what it makes available to modules that import it.
  • providers — services (module-level DI).
  • bootstrap — the root component (AppModule only).

Still valid, but standalone components + provideX() APIs are the modern default.

What does ChangeDetectorRef do (markForCheck, detectChanges, detach)?

ChangeDetectorRef gives manual control over change detection for one view — mostly used with OnPush:

  • markForCheck() — mark this component and its ancestors to be checked next cycle (after updating state Angular didn't notice).
  • detectChanges() — run change detection on this view immediately (synchronously).
  • detach() / reattach() — remove/restore this view from the CD tree for high-frequency perf tuning.
constructor(private cdr: ChangeDetectorRef) {}
update() { this.data = next; this.cdr.markForCheck(); }

What is an InjectionToken and why use one?

An InjectionToken is a unique, typed DI key for things that aren't classes — configuration objects, primitives, or interface-based values. Because TypeScript interfaces vanish at runtime, you can't inject by interface; a token gives you a type-safe handle instead.

You create one with new InjectionToken<AppConfig>('app.config') and provide it with useValue (or a factory).

export const API_URL = new InjectionToken<string>('API_URL');

providers: [
  {
    provide: API_URL,
    useValue: 'https://api.example.com'
  }
];

constructor(@Inject(API_URL) private apiUrl: string) {}

Explain useClass, useValue, useExisting, useFactory.

Providers tell the DI system how to produce the value for a token:

  • useClass — instantiate a class (swap one implementation for another).
  • useValue — supply a ready-made constant/object.
  • useExisting — alias one token to another existing instance (share, don't duplicate).
  • useFactory — build the value with a function, optionally using injected deps or runtime logic.

Add multi: true to register several providers under one token (e.g. HTTP interceptors).

// useClass
{ provide: Logger, useClass: ConsoleLogger }

// useValue
{ provide: API_URL, useValue: 'https://api.example.com' }

// useExisting
{ provide: OldLogger, useExisting: Logger }

// useFactory
{
  provide: API_URL,
  useFactory: () => environment.apiUrl
}

How do you create a custom pipe?

Create a class decorated with @Pipe({ name: 'myPipe' }) that implements PipeTransform, and write the transform(value, ...args) method returning the transformed result. Use it as {{ value | myPipe:arg }}.

Keep pipes pure and side-effect-free (no HTTP, no mutation) — pure pipes are cached until the input reference changes, so they're cheap.

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

How do you build a custom attribute directive?

Create a class decorated with @Directive({ selector: '[appHighlight]' }). Attribute directives change the appearance or behaviour of an existing element.

Inside, use @HostListener to react to host events, @HostBinding to set a host class/style/attribute, and @Input to accept values from the parent. Prefer these over writing to the DOM directly.

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {

  @HostBinding('style.backgroundColor')
  background = '';

  @HostListener('mouseenter')
  onMouseEnter() {
    this.background = 'yellow';
  }

  @HostListener('mouseleave')
  onMouseLeave() {
    this.background = '';
  }
}

How do custom structural directives work (TemplateRef + ViewContainerRef)?

A structural directive changes the DOM by adding or removing content. Angular injects it two things: a TemplateRef (the template to render) and a ViewContainerRef (where to create or clear that view).

The directive decides when to call createEmbeddedView() or clear() — which is exactly how *ngIf works internally.

@Directive({
  selector: '[appIf]',
  standalone: true
})
export class IfDirective {
  constructor(
    private template: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}

  @Input() set appIf(show: boolean) {
    this.viewContainer.clear();

    if (show) {
      this.viewContainer.createEmbeddedView(this.template);
    }
  }
}

Input setter vs ngOnChanges — when to react to input changes?

Both let you react when an @Input changes, but they suit different cases:

  • Input setterset value(v) { ... } reacts immediately to one specific input and can transform it on the way in.
  • ngOnChanges — receives a SimpleChanges object with previous/current values for all changed inputs, so it's better when logic depends on several inputs together.

Setter for a single field; ngOnChanges for coordinated logic.

private _id = 0;

@Input()
set id(value: number) {
  this._id = value;
  this.reload(value);
}

get id() {
  return this._id;
}

How do you make a custom two-way bindable property?

A custom two-way binding needs a matched pair: an @Input() named value and an @Output() named valueChange that you emit whenever the value updates. Angular's [(value)] is just syntactic sugar for [value] + (valueChange) — the same banana-in-a-box as ngModel.

In Angular 17.2+, the signal-based model() API does this in one line.

// child.component.ts
@Input() value = 0;
@Output() valueChange = new EventEmitter<number>();

update(newValue: number) {
  this.value = newValue;
  this.valueChange.emit(newValue);
}

// parent.component.html
<app-counter [(value)]="count"></app-counter>

What is a FormArray and when do you use it?

FormArray is a form control that holds an indexed list of controls or groups. Use it when the number of fields is dynamic — multiple phone numbers, addresses, or invoice rows — where a fixed FormGroup shape won't do.

You mutate it at runtime with push(), removeAt(), and clear(), and iterate its controls in the template.

phones = this.fb.array([
  this.fb.control('')
]);

addPhone() {
  this.phones.push(this.fb.control(''));
}

removePhone(index: number) {
  this.phones.removeAt(index);
}

How do you write sync and async custom validators?

A validator is a function attached to a control that returns ValidationErrors (an error map) or null when valid.

  • Sync validator(control) => ValidationErrors | null; runs immediately for local rules.
  • Async validator — returns an Observable/Promise of the same; for checks that hit a server (e.g. is this username taken).

Register async validators on the third argument, and pair them with updateOn: 'blur' so they don't fire on every keystroke.

// Sync validator
function noSpaces(control: AbstractControl): ValidationErrors | null {
  return /\s/.test(control.value)
    ? { spaces: true }
    : null;
}

const username = new FormControl('', {
  validators: [Validators.required, noSpaces]
});

How do you read route params — snapshot vs observable?

Inject ActivatedRoute to read route parameters:

  • snapshot.paramMap — a one-time read; fine when the component is recreated for each navigation.
  • the paramMap observable — subscribe when the same component instance stays alive while params change (e.g. /user/1/user/2), so it reacts automatically.

When in doubt, use the observable.

// One-time read
const id = this.route.snapshot.paramMap.get('id');

// React to parameter changes
this.route.paramMap.subscribe(params => {
  const id = params.get('id');
  this.load(id);
});

What does a modern functional route guard look like?

Modern guards are plain functions, not classes. A CanActivateFn uses inject() to grab services and returns boolean, a UrlTree, or an Observable/Promise of those.

Return a UrlTree (e.g. router.parseUrl('/login')) to redirect declaratively, rather than calling router.navigate() inside the guard.

export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);

  return auth.isLoggedIn()
    ? true
    : router.createUrlTree(['/login']);
};

How do you handle HttpClient errors and retries?

Handle errors by piping RxJS operators onto the HttpClient call:

  • retry(n) (or retry({ delay })) to re-attempt transient failures.
  • catchError to handle or transform the error — log it, show a message, or return a fallback with of(...).

Centralise cross-cutting error handling (toasts, 401s, logging) in an HTTP interceptor instead of repeating it everywhere.

this.http.get<User>(url).pipe(
  retry(2),
  catchError(err => { this.notify(err); return of(null); })
)

What are route preloading strategies?

Lazy routes load on demand. A preloading strategy fetches some lazy bundles in the background after the app boots, so the next navigation feels instant.

  • NoPreloading (default) — nothing extra.
  • PreloadAllModules — download every lazy route in the background.
  • a custom strategy — preload only routes flagged in their data.
provideRouter(routes, withPreloading(PreloadAllModules))

ViewChild vs ViewChildren (and QueryList).

@ViewChild returns the first matching element/directive/component; @ViewChildren returns a QueryList of all matches. Both resolve in ngAfterViewInit.

QueryList is live — subscribe to its changes observable to react when items are added or removed (e.g. rows from an *ngFor).

@ViewChildren(ItemComponent)
items!: QueryList<ItemComponent>;

ngAfterViewInit() {
  this.items.changes.subscribe(() => {
    console.log(this.items.length);
  });
}

How do you load a component dynamically?

Dynamic loading creates a component at runtime rather than declaring it in a template — useful when the type isn't known until the app is running (dialogs, dashboards, plugin systems).

Get a ViewContainerRef (usually via @ViewChild on an <ng-container>) and call vcr.createComponent(MyComp); set inputs on componentRef.instance and call destroy() when done. The old ComponentFactoryResolver is deprecated.

@ViewChild('container', { read: ViewContainerRef })
container!: ViewContainerRef;

const ref = this.container.createComponent(MyWidgetComponent);
ref.setInput('title', 'Hello');

How does Angular's animation system work?

Angular animations live in @angular/animations. You declare a trigger made of states and transitions (each with an animate() timing), then attach it in the template with [@triggerName].

Special transitions :enter and :leave handle elements added to or removed from the DOM. It's best for state-based and route/enter-leave animations; plain CSS is fine for simple hovers.

trigger('fade', [
  transition(':enter', [
    style({ opacity: 0 }),
    animate('200ms ease-in', style({ opacity: 1 }))
  ])
])

What is SSR (Server-Side Rendering) and Hydration in Angular?

Server-Side Rendering (SSR) renders the app to HTML on the server before sending it to the browser — better SEO and a faster first contentful paint (via @angular/ssr).

Hydration is the follow-up step where the browser reuses that server-rendered DOM and simply attaches Angular's event listeners, instead of throwing it away and re-rendering. That avoids a flash of blank/duplicated content and cuts startup work.

// Enable client hydration
bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration()
  ]
});

What are deferrable views (@defer)?

@defer (v17) lazy-loads a block of the template and its JavaScript until a trigger fires — on idle (default), on viewport, on interaction, on hover, on timer, or when a condition.

It has @placeholder, @loading and @error slots, and is perfect for heavy, below-the-fold widgets (charts, editors, comments) — shrinking the initial bundle.

@defer (on viewport) {
  <heavy-chart />
} @placeholder {
  <div>Loading chart...</div>
} @loading {
  <div>Please wait...</div>
} @error {
  <div>Failed to load chart.</div>
}

What are signal inputs (input()) in Angular?

input() is the signal-based replacement for @Input(). It returns a read-only signal you read as name(), which updates automatically when the parent passes a new value.

Because it's a signal it composes naturally with computed() and effect() (often removing the need for ngOnChanges), and input.required<T>() marks an input as mandatory.

name = input.required<string>();

fullName = computed(() =>
  `Hello ${this.name()}`
);

What are signal queries in Angular?

Signal queries are the signal-based versions of the decorator queries: viewChild(), viewChildren(), contentChild(), contentChildren().

They return signals whose values update reactively when the queried element changes, so they work inside computed()/effect() and drop the old static-flag timing dance.

button = viewChild<ElementRef>('btn');

effect(() => {
  console.log(this.button());
});

How does Angular protect against XSS?

Cross-Site Scripting (XSS) injects malicious scripts into a page. Angular defends by treating every bound value as untrusted and sanitizing it based on context (HTML, style, URL): interpolation {{ }} escapes HTML, and [innerHTML] is stripped of scripts before rendering.

Only use DomSanitizer.bypassSecurityTrust... for content you fully control — never for user-supplied HTML.

// Angular sanitizes the HTML before rendering
<div [innerHTML]="userHtml"></div>

Smart (container) vs dumb (presentational) components.

Smart / container components know about services and the store: they fetch data, hold state, and handle events. Dumb / presentational components only take @Inputs and emit @Outputs — no dependencies.

Keeping presentation dumb makes those components trivial to reuse, test, and mark OnPush; push logic up, keep the leaves simple.

What is ControlValueAccessor and when do you implement it?

ControlValueAccessor (CVA) is the interface that lets a custom component behave like a native form control, so it works with formControlName, [(ngModel)], and validation.

Implement four methods — writeValue() (model → view), registerOnChange() (view → model), registerOnTouched(), and setDisabledState() — and register the component with the NG_VALUE_ACCESSOR multi-provider.

@Component({
  selector: 'app-rating',
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: RatingComponent,
    multi: true
  }]
})
export class RatingComponent implements ControlValueAccessor {
  value = 0;

  onChange = (_: number) => {};
  onTouched = () => {};

  writeValue(value: number): void {
    this.value = value;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    // update disabled state
  }

  setRating(value: number) {
    this.value = value;
    this.onChange(value);
    this.onTouched();
  }
}

How do you validate across multiple fields (e.g. password match)?

When a rule depends on more than one field — password vs confirm-password, start-date vs end-date — a single FormControl can't see its siblings, so the validator must be placed on the parent FormGroup, where it can read all controls.

Return the error on the group, then surface it via form.errors (or copy it onto the target control for inline display).

function matchPasswords(group: AbstractControl): ValidationErrors | null {
  const password = group.get('password')?.value;
  const confirm = group.get('confirmPassword')?.value;

  return password === confirm
    ? null
    : { passwordMismatch: true };
}

const form = this.fb.group(
  {
    password: [''],
    confirmPassword: ['']
  },
  {
    validators: matchPasswords
  }
);

What are typed reactive forms (v14+)?

Since Angular 14, reactive forms are strongly typed. FormControl, FormGroup and FormArray are generic, so value, valueChanges and getRawValue() all carry real types — giving autocomplete and compile-time safety.

Control values are T | null by default; add { nonNullable: true } (or use NonNullableFormBuilder) when null should never occur.

email = new FormControl('', {
  nonNullable: true,
  validators: [Validators.email]
});

// email.value is string

How do you react to form changes reactively?

Every control and group exposes valueChanges and statusChanges as Observables. Subscribe (or use them in a stream) to react to input reactively.

Pipe debounceTime + distinctUntilChanged for autosave or live search, and switchMap into the server call. Set updateOn: 'blur' to reduce noise.

this.form.get('country')!.valueChanges.pipe(
  switchMap(c => this.api.getCities(c))
).subscribe(cities => this.cities = cities);

What does updateOn: 'blur' | 'submit' do?

updateOn controls when a control syncs its value and runs validation:

  • 'change' (default) — on every keystroke.
  • 'blur' — when the field loses focus.
  • 'submit' — only when the form is submitted.

Use 'blur'/'submit' for expensive or async validators so they don't fire constantly.

const email = new FormControl('', {
  validators: [Validators.required, Validators.email],
  updateOn: 'blur'
});

What is a selector in Angular and why use it?

The selector in @Component/@Directive is the CSS selector that tells Angular where to instantiate the component/directive in a template — it works like a custom HTML tag.

It can match an element (app-user-card), an attribute ([appHighlight]), or a class. Prefix element selectors (app-) to avoid clashing with real HTML tags.

@Component({ selector: 'app-user', templateUrl: './user.html' })
// used as: <app-user></app-user>

What is the entry point of an Angular application?

main.ts is the entry point — it bootstraps the app. In modern standalone Angular that's bootstrapApplication(AppComponent, { providers }); the module-based form is platformBrowserDynamic().bootstrapModule(AppModule).

index.html hosts the root <app-root> tag, and angular.json points the build at main.ts.

// modern standalone bootstrap
bootstrapApplication(AppComponent, { providers: [provideRouter(routes)] });

What is the use of router-outlet?

<router-outlet> is the placeholder where the Router renders the component that matches the current route. As the URL changes, Angular swaps the component shown there while everything outside the outlet (nav, header) stays put.

Nested outlets host child routes, and named outlets (name="aux") host secondary/auxiliary views such as side panels.

<nav>…</nav>
<router-outlet></router-outlet> <!-- routed component renders here -->

How do you call a REST API in Angular?

Inject the HttpClient service (registered with provideHttpClient()) inside a dedicated service, not the component. Its methods (get/post/put/delete) return cold Observables — nothing fires until something subscribes.

Type the response (get<User[]>(url)), handle failures with catchError, and let components consume the stream via the async pipe. Keeping HTTP in services makes it reusable and testable.

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}
  getUsers() { return this.http.get<User[]>('/api/users'); }
  create(u: User) { return this.http.post<User>('/api/users', u); }
}

How do you make an API call that depends on another call's response?

When one call depends on another's result, flatten with switchMapgetUser().pipe(switchMap(u => getOrders(u.id))) — which also cancels a stale first call.

For independent calls run them in parallel with forkJoin (all complete, one emit) or combineLatest; for ordered writes use concatMap. Never nest .subscribe() calls.

this.api.getUser(id).pipe(
  switchMap(user => this.api.getOrders(user.id))
).subscribe(orders => this.orders = orders);

What is the difference between a component and a directive?

A component is a directive with a template — it owns and controls a view. A directive has no template of its own; it adds behaviour or changes an existing element (attribute directives like ngClass, structural ones like *ngIf).

Put another way: every component is a directive, but not every directive is a component.

Why do we use services in Angular?

  • Encapsulate reusable business logic and HTTP/API calls.
  • Share state between components (often via a BehaviorSubject or signals).
  • Provide a singleton instance through dependency injection.
  • Enforce separation of concerns — components render, services do the work — which keeps everything testable.

What are Angular route guards and their types?

Guards control access to and movement between routes:

  • CanActivate — can the user enter this route? (auth checks)
  • CanActivateChild — the same for child routes.
  • CanDeactivate — can they leave? (unsaved-changes prompt)
  • CanMatch — should this route config even match/load? (replaces the deprecated CanLoad)
  • Resolve — pre-fetch data before activation.

For auth, return a UrlTree to redirect to login.

export const authGuard: CanActivateFn = () =>
  inject(AuthService).isLoggedIn() || inject(Router).createUrlTree(['/login']);

How would you debug "Cannot read properties of undefined" in Angular?

It means you read a property on something that isn't there yet — almost always an async/timing issue (data rendered before it arrives, or an input read too early). Fixes:

  • Guard the template with @if (data) / *ngIf, or use the safe-navigation operator data?.name.
  • Read @Input values in ngOnInit, not the constructor; initialise variables ([]/null) before use.
  • Read @ViewChild in ngAfterViewInit, and use the stack trace to find the exact line.
<div>{{ user?.profile?.name }}</div>
<div *ngIf="user as u">{{ u.name }}</div>

What are the options for state management in Angular?

Match the tool to the scale — don't over-engineer:

  • Local — component fields or signals.
  • Shared & simple — a service exposing a BehaviorSubject or signal.
  • Large / complex / many consumers — NgRx or NgRx SignalStore for a structured, DevTools-traceable Redux-style store.

constructor vs ngOnInit — what's the difference?

The constructor is a TypeScript feature that runs when the class is created — Angular uses it only to inject dependencies. At that point @Input values and the view are not ready yet.

ngOnInit is an Angular lifecycle hook that runs after the first change detection, once inputs are set. Rule of thumb: inject in the constructor, initialise in ngOnInit (data fetching and setup that needs inputs).

constructor(private api: ApiService) {}   // DI only
ngOnInit() { this.user = this.api.getUser(this.id); } // id is ready here

What is trackBy in *ngFor and why use it?

By default *ngFor tracks items by object identity, so when the array reference changes (e.g. after an HTTP refresh) Angular destroys and rebuilds every DOM row. A trackBy function tells Angular how to identify an item — usually by id — so it reuses existing rows and only re-renders what actually changed.

This is a big win for large lists and preserves focus/scroll/animation state. The new @for requires a track expression.

<li *ngFor="let u of users; trackBy: trackById">{{ u.name }}</li>

trackById(index: number, u: User) { return u.id; }
// v17 control flow: @for (u of users; track u.id) { ... }

forRoot() vs forChild() — what's the difference?

This pattern lets a shareable module register services once but be imported many times. forRoot() is called once in the app root to register singleton providers and root configuration. forChild() is called in feature modules to add routes/config without re-registering (and duplicating) those singletons.

The classic example is RouterModule.forRoot(routes) at the root and RouterModule.forChild(routes) in features.

// app.module.ts
RouterModule.forRoot(appRoutes)

// feature.module.ts
RouterModule.forChild(featureRoutes)

What causes ExpressionChangedAfterItHasBeenCheckedError?

ExpressionChangedAfterItHasBeenCheckedError is a dev-mode-only safety check: Angular runs change detection a second time and, if a bound value changed between the two passes, throws to warn of an inconsistent view.

It usually happens when you mutate state in a hook that runs after the view was checked (like ngAfterViewInit). Fixes: move the change earlier (ngOnInit), defer it a tick (setTimeout/Promise.resolve), or call cdr.detectChanges().

// Fixes:
// 1) move the update earlier (ngOnInit)
// 2) defer it: setTimeout(() => this.value = x) or a microtask
// 3) cdRef.detectChanges() after the change

Observable vs Promise — what's the difference?

  • Promise — a single future value; eager (runs immediately); not cancellable; no operators.
  • Observable — a stream of 0..∞ values over time; lazy (runs only on subscribe); cancellable via unsubscribe; with a rich operator set (map, switchMap, debounceTime).

Angular's HttpClient returns Observables, which is why they dominate Angular apps.

// promise: fires now, one value
fetch('/api').then(r => r.json());
// observable: fires on subscribe, cancellable
this.http.get('/api').subscribe(data => ...);

How would you handle rapid user actions (e.g. live search or AI analysis requests) to avoid multiple API calls and outdated results?

Combine two operators on the input stream. debounceTime(300) waits until the user pauses typing/clicking, cutting needless calls, and distinctUntilChanged skips duplicate terms.

Then switchMap into the request — it cancels the previous in-flight call whenever a new one starts, so only the latest result is processed and stale responses can't arrive out of order.

this.search$.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(query => this.api.search(query))
).subscribe(result => {
  this.results = result;
});

Your page displays 10,000 records and scrolling becomes very slow. How would you optimize it?

Don't render 10,000 DOM nodes. Layer three fixes:

  • Virtual scrollingcdk-virtual-scroll-viewport renders only the rows currently visible.
  • trackBy — so updates reuse existing rows instead of rebuilding the list.
  • OnPush + immutable data — to cut change-detection work.

Better still, paginate or lazy-load from the server so the client never holds all 10k at once.

<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
  <div *cdkVirtualFor="let user of users; trackBy: trackById">
    {{ user.name }}
  </div>
</cdk-virtual-scroll-viewport>

trackById(index: number, user: User) {
  return user.id;
}

How do you prevent memory leaks caused by RxJS subscriptions?

Long-lived subscriptions that never complete are the usual leak — they pile up duplicate HTTP calls and grow memory. Clean them up:

  • Prefer the async pipe, which unsubscribes automatically.
  • For manual subscriptions, use takeUntilDestroyed() (v16+) or takeUntil(destroy$) completed in ngOnDestroy.

HTTP calls complete on their own; streams like valueChanges, intervals and Subjects do not — those are what you must tear down.

this.userService.users$
  .pipe(takeUntilDestroyed())
  .subscribe(users => this.users = users);

How would you prevent users from submitting the same form multiple times by clicking the Save button repeatedly?

Guard against double submits on two layers. In the UI, disable the Save button while the request is in flight (a loading flag). In the stream, use exhaustMap, which ignores new clicks until the current request finishes — unlike switchMap, which would cancel and re-fire.

fromEvent(saveBtn, 'click')
  .pipe(
    exhaustMap(() => this.api.save(data))
  )
  .subscribe();

How would you share data between unrelated Angular components?

For unrelated components, don't reference each other directly — communicate through a shared service that exposes a BehaviorSubject/Subject (or signals) as an event bus; use an NgRx store when the state is shared across many features.

Direct parent–child communication still uses @Input() and @Output().

@Injectable({ providedIn: 'root' })
export class UserService {
  private userSubject = new BehaviorSubject<User | null>(null);
  user$ = this.userSubject.asObservable();

  setUser(user: User) {
    this.userSubject.next(user);
  }
}

Your Angular application has become slow because the initial bundle size is very large. How would you improve the loading performance?

A large initial bundle means the browser downloads everything before the first screen. Split the app into feature areas and lazy-load them via the router (loadChildren / loadComponent), so only the code the current route needs is fetched up front.

Add a preloading strategy for snappy later navigation, and inspect the bundle with source-map-explorer / budgets to find the bloat.

const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module')
      .then(m => m.AdminModule)
  }
];

Multiple components call the same API, causing duplicate network requests. How would you optimize it?

Share one request instead of firing many. Expose the call from a service and pipe shareReplay(1): the first subscriber triggers the HTTP request, and every later subscriber gets the cached result with no extra network call.

Add invalidation / a TTL so the cache doesn't go stale.

users$ = this.http.get<User[]>('/api/users').pipe(
  shareReplay(1)
);

How would you prevent unauthenticated users from accessing protected pages?

Protect the route with a CanActivate guard. Before navigation it checks whether the user is authenticated; if not, it returns a UrlTree to /login (optionally with a returnUrl) — a declarative redirect, which is cleaner than calling router.navigate() inside the guard. Guards run before the component loads.

export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);

  return auth.isLoggedIn()
    ? true
    : router.createUrlTree(['/login']);
};

A component is re-rendering frequently and performance is poor. How would you optimize Angular Change Detection?

Switch the component to ChangeDetectionStrategy.OnPush. Angular then checks it only when an @Input reference changes, an event fires inside it, or an async pipe emits — cutting most needless cycles.

It requires immutable inputs: replace objects/arrays ({...obj}, [...arr]) instead of mutating in place, since Angular compares by reference. Call markForCheck() when you update state outside Angular's triggers.

@Component({
  selector: 'app-users',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UsersComponent {}

How would you implement an auto-save feature for a form without sending an API request on every keystroke?

Subscribe to form.valueChanges and debounce it rather than saving on every keystroke:

valueChanges.pipe(debounceTime(500), distinctUntilChanged(), switchMap(v => api.save(v)))debounceTime waits for a pause, distinctUntilChanged skips no-op changes, and switchMap cancels an in-flight save when newer edits arrive. Show a "Saving… / Saved" status for feedback.

this.form.valueChanges.pipe(
  debounceTime(1000),
  distinctUntilChanged(),
  switchMap(value => this.api.saveDraft(value))
).subscribe();

How would you handle HTTP errors consistently across the entire Angular application?

Handle errors in one place with two complementary layers: an HTTP interceptor that pipes catchError for API/network errors (401s, 5xx, toasts, retries), and a global ErrorHandler for uncaught runtime errors.

That centralises logging and user messaging instead of duplicating try/catch across components and services.

intercept(req: HttpRequest<any>, next: HttpHandler) {
  return next.handle(req).pipe(
    catchError(error => {
      console.error(error);
      return throwError(() => error);
    })
  );
}

How would you handle an expired JWT token without forcing the user to log in again?

Do it silently inside an HTTP interceptor. On a 401 Unauthorized, call the refresh-token endpoint once, store the new access token, and retry the failed request. Queue any other 401s that arrive during the refresh (a BehaviorSubject/lock) so you refresh only once, and log the user out only if the refresh itself fails.

401 Error
↓
Refresh Token API
↓
New Access Token
↓
Retry Original Request

How would you display upload progress while uploading a large file?

Ask HttpClient for progress events: http.post(url, formData, { reportProgress: true, observe: 'events' }).

Listen for HttpEventType.UploadProgress and compute the percentage as 100 * event.loaded / event.total to drive a progress bar; HttpEventType.Response signals completion.

this.http.post(url, file, {
  reportProgress: true,
  observe: 'events'
}).subscribe(event => {
  if (event.type === HttpEventType.UploadProgress) {
    const progress = Math.round(100 * event.loaded / event.total!);
  }
});

What are microfrontends and how do they differ from a normal frontend?

A microfrontend architecture splits one web app into independently built, tested and deployed pieces, each owned end-to-end by a different team, and composed together in the browser at runtime.

A normal (monolithic) frontend is one codebase, one build, one deploy — every team's change ships in the same bundle, on the same release train.

The real difference isn't technical, it's organisational: microfrontends trade runtime and tooling complexity for independent deployability. In Angular this is usually done with Module Federation (Webpack 5 / Native Federation), where a shell loads remotes at runtime.

// shell — webpack.config.js
new ModuleFederationPlugin({
  remotes: {
    orders: 'orders@https://orders.app.com/remoteEntry.js'
  },
  shared: {
    '@angular/core':   { singleton: true, strictVersion: true },
    '@angular/router': { singleton: true, strictVersion: true }
  }
});

// shell route — the remote is fetched only when visited
{
  path: 'orders',
  loadChildren: () => import('orders/Module').then(m => m.OrdersModule)
}

How do you build a scalable enterprise Angular application?

First, define the axis — this is the trap. On a frontend, "scalable" almost never means server traffic; it means the codebase and the team can keep growing without the app rotting.

  • Code scale — feature-based structure with enforced boundaries, so 200k lines stay navigable.
  • Team scale — clear ownership; teams don't collide in the same files.
  • Runtime scale — lazy loading, OnPush, virtual scrolling, performance budgets so it stays fast as features pile on.
  • Data scale — pagination and virtualisation; never render 10k rows.
// Nx — enforce boundaries so 'scalable' is checked by the linter, not a wiki page
"@nx/enforce-module-boundaries": ["error", {
  "depConstraints": [
    { "sourceTag": "scope:orders", "onlyDependOnLibsWithTags": ["scope:orders", "scope:shared"] },
    { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui", "type:data-access"] }
  ]
}]

Walk me through the architecture of your Angular application.

Answer in layers, and give a reason for each choice — the interviewer is testing whether you designed it or inherited it.

  • Structurecore/ (singletons: auth, interceptors, error handling), shared/ (dumb reusable UI), features/ (one lazy-loaded folder per domain).
  • Components — smart/container components fetch data; dumb/presentational ones take @Input and emit @Output, so they're trivially testable.
  • Services + DI — HTTP and business logic live in services, never components.
  • State — local first; a store only where state is genuinely shared.
  • Cross-cutting — interceptors for auth/errors, guards for routes.
src/app/
  core/            # provided once: interceptors, guards, auth
  shared/          # dumb UI, pipes, directives — no feature imports
  features/
    orders/
      data-access/   # OrdersService, models
      feature-list/  # smart component (routed)
      ui/            # dumb components
  app.routes.ts    # lazy: loadChildren / loadComponent

What's new in Angular 14, 15 and 16? What did migrating between them involve?

  • Angular 14standalone components (developer preview), typed reactive forms, the inject() function usable in functions, protected route guards as plain functions, extended template diagnostics.
  • Angular 15 — standalone APIs stable (loadComponent routing, functional guards/interceptors become the norm), directive composition API, NgOptimizedImage stable, MDC-based Material.
  • Angular 16Signals (developer preview), takeUntilDestroyed + DestroyRef, required inputs (@Input({required: true})), route params as component inputs, esbuild dev server preview, non-destructive hydration for SSR.

Migration is ng update @angular/core@15 @angular/cli@15 per major — schematics rewrite most breaking changes; the practical work is usually TypeScript/Node version bumps, Material MDC style breakages (v15), and dependency alignment.

# one major at a time, run the schematics
ng update @angular/core@15 @angular/cli@15
ng update @angular/material@15   # the usual pain point (MDC)

*ngIf vs [hidden] — what's the difference?

*ngIf adds/removes the element from the DOM: when false, the component is destroyed (ngOnDestroy runs, subscriptions in it die, no change detection cost). [hidden] just toggles display: none — the element stays in the DOM, stays instantiated, and keeps being change-detected.

  • Expensive component, rarely shown → *ngIf.
  • Cheap element toggled constantly (avoid re-init cost / preserve internal state like scroll or form input) → [hidden].
<heavy-chart *ngIf="showChart"></heavy-chart>      <!-- destroyed when false -->
<div [hidden]="collapsed">cheap, keeps DOM state</div> <!-- display:none only -->

setValue vs patchValue?

Both write values into a FormGroup/FormControl:

  • setValue(obj)strict, full shape required: the object must contain a value for every control; missing or extra keys throw. Use when the payload should exactly match the form.
  • patchValue(obj)lenient, partial update: sets what's provided, silently ignores the rest (including typos in key names!).

Both fire valueChanges; pass { emitEvent: false } to update silently (crucial when writing from inside a valueChanges handler to avoid loops).

this.form.setValue({ name: u.name, email: u.email });   // must be complete
this.form.patchValue({ email: u.email });               // partial is fine
this.form.patchValue({ email: x }, { emitEvent: false }); // no valueChanges loop

dirty vs touched vs pristine? valid/invalid/pending? How do you enable/disable controls reactively?

Two independent state axes on every AbstractControl:

  • Value axis: pristine (never changed) ↔ dirty (value changed by the user).
  • Visit axis: untouchedtouched (control was blurred).

Validity: valid / invalid / pending (async validator running) / disabled (excluded from validation and from form.value!).

Enable/disable via API — ctrl.disable() / ctrl.enable(), typically inside a valueChanges subscription; never [disabled] in the template with reactive forms (Angular warns).

this.form.get('country')!.valueChanges.subscribe(c => {
  const state = this.form.get('state')!;
  c === 'IN' ? state.enable() : state.disable();   // reactive, not [disabled]
});

// template:
// <div *ngIf="email.invalid && (email.dirty || email.touched)">...

routerLink vs router.navigate() vs navigateByUrl()?

  • routerLink — declarative, in templates: renders a real <a href> (middle-click/SEO friendly); pairs with routerLinkActive.
  • router.navigate(['users', id]) — programmatic, takes a commands array; supports relative navigation via { relativeTo: this.route }, plus queryParams, fragment, state.
  • router.navigateByUrl('/users/42') — programmatic, takes an absolute URL string, always from root; query params go inside the string.

Rule of thumb: template link → routerLink; navigate after logic (form saved, login done) → navigate(); redirect to a fully-formed URL (e.g. stored returnUrl) → navigateByUrl.

// after login: go back where the guard bounced from
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') ?? '/';
this.router.navigateByUrl(returnUrl);

// relative: /orders/42 -> /orders/42/items
this.router.navigate(['items'], { relativeTo: this.route });

Child routes, named (auxiliary) outlets, and the wildcard route — how do they work?

Child routes — nested children: [] render into the parent component's own <router-outlet>: layout stays, only the inner area swaps (tabs, master-detail).

Named outlets<router-outlet name="panel"> + outlet: 'panel' on the route lets independent content (chat sidebar, preview pane) have its own URL segment: /inbox(panel:compose).

Wildcardpath: '**' catches everything unmatched (404 page). Route order matters: first match wins, so ** must be last and specific paths go before parameterized ones.

const routes: Routes = [
  { path: 'orders', component: OrdersShell, children: [
      { path: '', component: OrderList },
      { path: ':id', component: OrderDetail },   // renders inside OrdersShell's outlet
  ]},
  { path: 'compose', component: ComposeCmp, outlet: 'panel' },
  { path: '**', component: NotFoundCmp },        // ALWAYS last
];

PathLocationStrategy vs HashLocationStrategy?

  • PathLocationStrategy (default) — clean URLs (/users/42) via the HTML5 History API. Requires the server to serve index.html for every route (a rewrite/fallback rule) — otherwise a refresh or deep link on /users/42 is a 404.
  • HashLocationStrategy — routes after a hash (/#/users/42). The browser never sends the fragment to the server, so no server config needed — but URLs are uglier, and SEO/SSR are out.
# nginx fallback for PathLocationStrategy
location / {
  try_files $uri $uri/ /index.html;
}

// hash only when you can't touch the server (file://, legacy hosting):
provideRouter(routes, withHashLocation())

How do you pass data between routes? And what is RouteReuseStrategy?

Options by durability:

  • Route/query params — survives refresh and sharing; the default for identity (/orders/42?tab=items). Pass ids, not objects.
  • Navigation staterouter.navigate([...], { state: { draft } }), read via history.state. Invisible in the URL but lost on refresh — good for optional context only.
  • Shared service / store — for real data; the target route should still be able to fetch by id if state is empty.
  • Route data / resolvers — static config or prefetched data attached to the route.

RouteReuseStrategy (awareness): by default Angular destroys a component on leave and recreates on return; a custom strategy can detach and store the component (e.g. keep a filtered list's state and scroll when going list → detail → back).

// list -> detail keeping context out of the URL
this.router.navigate(['/orders', id], { state: { from: 'search' } });
// detail:
const from = this.router.getCurrentNavigation()?.extras.state?.['from']
          ?? history.state.from;   // after refresh: undefined -> fall back gracefully

What's the order of multiple interceptors? And the HttpParams/HttpHeaders immutability common Mistakes?

Interceptors run in registration order for requests, reverse order for responses — an onion: first registered = outermost layer. Register auth before logging and the log sees the token; order is the array order in withInterceptors([auth, logging]) (or multi-provider order for class-based ones).

Immutability gotcha: HttpRequest, HttpHeaders, HttpParams are all immutable — headers.set(...) and params.append(...) return a new object. Calling them without using the return value silently does nothing.

// WRONG — result thrown away:
// req.headers.set('Authorization', token);

// RIGHT — clone with the new immutable pieces:
const authed = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
return next(authed);

let params = new HttpParams().set('page', '1');
params = params.set('size', '20');   // reassign!

What is NgZone.runOutsideAngular() and when do you use it? (And web workers?)

Zone.js triggers change detection after every patched async event. runOutsideAngular(fn) runs code where those events don't trigger CD — for high-frequency work that doesn't change bound state on every tick: mousemove/scroll listeners, requestAnimationFrame loops, polling timers, third-party libs (charts, maps).

When a result does need to reach the UI, re-enter with zone.run(() => ...).

Web workers (awareness): for CPU-heavy computation (parsing, crunching) move the work off the main thread entirely — ng generate web-worker scaffolds one; communication is postMessage.

constructor(private zone: NgZone) {}

ngOnInit() {
  this.zone.runOutsideAngular(() => {
    fromEvent(window, 'mousemove').pipe(throttleTime(50)).subscribe(e => {
      this.updateCanvas(e);                       // no CD per event
      if (this.crossedThreshold(e))
        this.zone.run(() => this.showHint = true); // CD only when UI must change
    });
  });
}

What is a PWA? How does the Angular service worker cache assets vs API calls?

A Progressive Web App is a web app that's installable, offline-capable and fast, built on three pillars: HTTPS, a web app manifest (name, icons — enables install), and a service worker (a background script that intercepts network requests and serves from cache).

ng add @angular/pwa wires Angular's service worker, configured via ngsw-config.json:

  • assetGroups — app shell files (js/css/index.html): prefetch (download all at install) or lazy; versioned by content hash, updated as a whole so the app is always a consistent build.
  • dataGroups — API responses: strategy freshness (network-first with timeout fallback to cache) or performance (cache-first until maxAge), with maxSize/maxAge per group.
// ngsw-config.json
"dataGroups": [{
  "name": "api-catalog",
  "urls": ["/api/catalog/**"],
  "cacheConfig": {
    "strategy": "performance",     // cache-first
    "maxSize": 100, "maxAge": "1d"
  }
}]

Which Angular CLI commands do you use daily? What does ng build --configuration production actually do?

Daily: ng serve (dev server + HMR/watch), ng generate component|service|guard, ng test, ng lint, ng build, ng update, ng add (install + run a library's setup schematic).

ng build --configuration production: AOT compilation, tree-shaking + minification, style/script optimization, output hashing for cache-busting, source maps off, budgets enforced (build fails if bundles exceed angular.json limits), fileReplacements swapping in environment.prod.ts.

Schematics (awareness) are the code generators behind ng generate/add/update — libraries ship them so ng add @angular/material can edit your project, and ng update runs migration schematics.

ng build --configuration production
ng build --stats-json && npx source-map-explorer dist/**/*.js  # what's IN the bundle
ng update @angular/core@16 @angular/cli@16                     # migration schematics

What are the DI resolution modifiers @Self, @Optional, @SkipSelf, @Host?

They tune where the injector search looks and what happens on a miss (default: walk up from the component's own injector to root, throw if not found):

  • @Optional() — inject null instead of throwing. For "use it if present" dependencies.
  • @Self() — only this component/directive's own injector; don't walk up.
  • @SkipSelf() — start from the parent injector; skip your own. Classic for a directive finding a parent instance of itself (nested menus), or a service checking it isn't provided twice.
  • @Host() — stop the search at the host component (a directive may look at its component but not beyond).
// directive grabs its OWN ngModel, not an ancestor's:
constructor(@Self() private ngControl: NgControl) {}

// guard against double forRoot():
constructor(@Optional() @SkipSelf() parent?: CoreModule) {
  if (parent) throw new Error('CoreModule imported twice');
}

What are the approaches to internationalization (i18n) in Angular?

Two main routes:

  • Built-in @angular/localize — mark text with the i18n attribute in templates (and $localize in code), extract with ng extract-i18n into an XLIFF/XMB file, hand it to translators, then build one bundle per locale (compile-time). Zero runtime lookup cost, but switching language means loading a different build/URL.
  • Runtime libraries@ngx-translate/core or Transloco load JSON translation files and swap language live via a pipe/directive ({{ 'KEY' | translate }}), no rebuild needed.

Both handle pluralization (ICU {count, plural, ...}) and interpolation. Locale-aware DatePipe/CurrencyPipe/DecimalPipe come from LOCALE_ID plus registered locale data.

<!-- native @angular/localize -->
<h1 i18n="@@homeTitle">Welcome</h1>
<span i18n>{count, plural, =0 {no items} =1 {one item} other {{{count}} items}}</span>

// runtime (ngx-translate / Transloco)
{{ 'HOME.TITLE' | translate }}
this.translate.use('fr');

What is a template reference variable in Angular?

A template reference variable is a #name you declare in the template to get a handle to something, then reference it elsewhere in the same template:

  • On a plain element — the DOM element (e.g. #box → the <input>).
  • On a component/directive — its instance (or the exported directive, e.g. #f="ngForm").

Scope is the template only; to use it in the class, query it with @ViewChild.

<!-- element ref -->
<input #box (keyup)="0">
<button (click)="box.focus()">Focus</button>
<p>{{ box.value }}</p>

<!-- exported directive ref -->
<form #f="ngForm" (ngSubmit)="save(f.value)">...</form>

What is Angular Material?

Angular Material is Google's official UI component library implementing Material Design for Angular — ready-made, accessible, themeable components (buttons, form fields, dialogs, tables, datepickers, snackbars, etc.).

You add it with ng add @angular/material, import the standalone components/modules you need, and theme it with SCSS. It's built on the Angular CDK (Component Dev Kit), which provides the lower-level, unstyled primitives (overlay, accessibility, drag-and-drop, virtual scrolling) you can also use to build your own components.

ng add @angular/material

// use a component
import { MatButtonModule } from '@angular/material/button';
// <button mat-raised-button color="primary">Save</button>

What is a Resolver in Angular, and how do you use one?

A Resolver pre-fetches data before a route activates, so the component renders with its data already present — no empty flash or in-component spinner on entry.

Write a functional resolver (ResolveFn) that returns the data (value / Observable / Promise), attach it to the route via resolve: { key: fn }, and read it in the component from ActivatedRoute.data (or the snapshot).

Trade-off: navigation waits for the resolver, so keep it fast and handle errors — a slow resolver makes the whole route feel slow.

// user.resolver.ts
export const userResolver: ResolveFn<User> =
  (route) => inject(UserService).getUser(route.paramMap.get('id')!);

// routes
{ path: 'users/:id', component: UserComponent, resolve: { user: userResolver } }

// component
constructor(private route: ActivatedRoute) {
  this.user = this.route.snapshot.data['user'];
}

What is FormControl in Angular Reactive Forms?

FormControl represents a single input's value and validation state — the atomic building block of reactive forms (FormGroup and FormArray compose FormControls).

You create it with an initial value and validators, bind it with [formControl], and use its API: .value, .valid, .errors, .valueChanges (an Observable), and setValue()/patchValue().

email = new FormControl('', [Validators.required, Validators.email]);

// template
// <input [formControl]="email">
// <span *ngIf="email.errors?.['required']">Required</span>

this.email.valueChanges.subscribe(v => console.log(v));

What is FormGroup in Angular Reactive Forms?

FormGroup groups related controls into one object: its value is a key→value map, and the group is valid only when all its children are valid.

Build it with FormBuilder (fb.group({...})), bind [formGroup] on the <form> and formControlName on each input, then read form.value on submit. FormGroups can be nested for structured data.

form = this.fb.group({
  name:  ['', Validators.required],
  email: ['', [Validators.required, Validators.email]],
});

// template
// <form [formGroup]="form" (ngSubmit)="save(form.value)">
//   <input formControlName="name">
//   <input formControlName="email">
// </form>

ViewChild vs ContentChild — what's the difference?

Both grab a child, but from different places:

  • @ViewChild — queries the component's own template (its view).
  • @ContentChild — queries projected content — the markup a parent passes between the component's tags, rendered through <ng-content>.

Timing: a @ViewChild is ready in ngAfterViewInit; a @ContentChild in ngAfterContentInit. Use @ViewChildren/@ContentChildren (a QueryList) for multiple.

@Component({ template: `<h1 #title></h1> <ng-content></ng-content>` })
class Card {
  @ViewChild('title') title!: ElementRef;      // from OWN template
  @ContentChild(IconComponent) icon!: IconComponent; // PROJECTED by parent
}
// parent: <app-card><app-icon/></app-card>  <-- app-icon is ContentChild