Huru
Technical Interviews

Angular Interview Questions and Answers (2026): Signals, Zoneless, RxJS and Angular 22

Angular interview questions with answers checked against angular.dev and the Angular team's release posts: components and standalone, templates and control flow, signals (computed, effect, linkedSignal, resource), change detection with OnPush and zoneless, dependency injection with inject() and @Service, RxJS, routing and guards, Signal Forms, HTTP, @defer and SSR, testing with Vitest, what changed in Angular 21 and 22, output puzzles and coding exercises.

By Amine Boukioud Updated 17 min read
The Huru mascot taps the top node of a glowing network on a glass board, and only the nodes connected below it light up, next to a laptop showing code

The short answer: Angular interviews test whether you can build components the current way and explain why it works. Expect questions on templates (bindings, @if/@for/@switch, pipes), signals (signal, computed, effect, linkedSignal, resource), change detection (OnPush, zoneless), dependency injection, RxJS, routing, forms and testing. Interviewers also check that you’re current. New applications have been zoneless since Angular 21 (November 2025). Angular 22 (June 3, 2026) made Signal Forms, Angular Aria and resource/httpResource stable, made OnPush the default, and added @Service and injectAsync. The answers below are checked against angular.dev, the Angular team’s release posts and the Angular repository, with sources at the end.

Angular is written in TypeScript, so brush up on both layers first: our TypeScript interview questions and JavaScript interview questions.

Angular 21 and 22: what changed

Questions about recent versions are a quick way for interviewers to tell who has worked with modern Angular. The short version:

VersionReleasedWhat it changed
Angular 20May 28, 2025Signal primitives (signal, effect, linkedSignal, signal inputs and queries) stable; incremental hydration stable. Zoneless became stable in v20.2
Angular 21November 19, 2025New apps no longer include zone.js (zoneless by default); Vitest is the default, stable test runner; Signal Forms (experimental); Angular Aria (developer preview)
Angular 22June 3, 2026Signal Forms, Angular Aria, resource and httpResource stable; OnPush is the default for new apps and ChangeDetectionStrategy.Default is renamed Eager; @Service; injectAsync; arrow functions and spread syntax in templates; multiple @case matching and exhaustive @switch; Webpack-based builders deprecated
Angular 22.2September 23, 2026@boundary error boundaries in templates, in developer preview

Angular has also changed its release rhythm. Until v22 there was a new major version every six months; from v22 on, the team plans “a major release every 12 months,” with v23 expected around June 2027. Each major is supported for 24 months. Versions up to v19 are no longer supported, and v20’s long-term support ends in November 2026.

Fundamentals

“What is Angular, and how is it different from React?”

Angular is a full framework from Google, written in TypeScript. It ships with a router, forms, an HTTP client, dependency injection, a CLI and testing tools, so most decisions are already made for you. React is a UI library: you choose the router, data fetching and state tools yourself. Angular templates are HTML with Angular syntax, compiled ahead of time; React uses JSX. A good answer also mentions trade-offs: Angular’s conventions make large codebases consistent, and there’s more to learn up front. For the React side, see our React interview questions.

“What is a component? What does standalone mean?”

A component is a TypeScript class with a @Component decorator that pairs a template, styles and behavior. A standalone component lists its own dependencies in imports instead of being declared in an NgModule. The Angular team “recommends using standalone components instead of NgModule for all new code,” and a component now has to be marked standalone: false to be declared in an NgModule. Applications start with bootstrapApplication rather than bootstrapModule.

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

@Component({
  selector: 'app-cart-item',
  template: `
    <h3>{{ name() }}</h3>
    <p>{{ quantity() }} × {{ price() }} = {{ total() }}</p>
    <button (click)="add()">Add one</button>
  `,
})
export class CartItem {
  name = input.required<string>();
  price = input(0);
  quantity = signal(1);
  total = computed(() => this.quantity() * this.price());
  added = output<number>();

  add() {
    this.quantity.update(q => q + 1);
    this.added.emit(this.quantity());
  }
}

“What kinds of data binding are there?”

  • Interpolation: {{ total() }} renders a value as text.
  • Property binding: [disabled]="isSaving()" sets a DOM property or a component input.
  • Event binding: (click)="add()" runs a method when an event fires.
  • Two-way binding: [(value)]="score", the “banana in a box,” combines a property and an event. With signals, the child declares a model() input, which the docs say to use “when you want a component to support two-way binding.” [(ngModel)] is the forms version.

“Signal inputs or @Input()?”

input() creates a read-only signal; input.required() makes the parent provide a value (“Angular enforces that required inputs must be set”); and a transform function can convert the value, for example a string attribute to a boolean. The older decorator-based @Input “remains fully supported,” but signal inputs work naturally with computed and need no ngOnChanges to react to changes. output() replaces @Output() with EventEmitter.

“What’s the difference between @if/@for and *ngIf/*ngFor?”

The built-in control flow blocks, stable since v18, are part of the template syntax, so you don’t import anything:

@if (user(); as u) {
  <p>Welcome back, {{ u.name }}</p>
} @else {
  <a routerLink="/login">Sign in</a>
}

@for (item of items(); track item.id) {
  <app-cart-item [name]="item.name" [price]="item.price" />
} @empty {
  <p>Your cart is empty.</p>
}

The track expression “allows Angular to maintain a relationship between your data and the DOM nodes,” so choose a property that uniquely identifies each item. Tracking by index when items can be reordered or removed makes Angular reuse the wrong DOM nodes. $index, $first, $last, $even, $odd and $count are available inside @for. @switch doesn’t fall through; since v22, consecutive @case lines can share one block, and @default never; makes the compiler flag any unhandled value of a union type.

“What are directives?”

Components are directives with a template. Attribute directives change the appearance or behavior of an element ([class], [style] bindings, or your own, such as a tooltip). Structural directives add or remove DOM: *ngIf and *ngFor are the classic ones, now mostly replaced by control flow blocks. Host directives let a component reuse a directive’s behavior through hostDirectives.

“Pure vs impure pipes?”

Pipes transform values in templates, like {{ price | currency }}. “By default, all pipes are considered pure”: Angular reruns a pure pipe only when a primitive input changes or an object reference is replaced, so mutating an array in place won’t update a pure pipe’s output. An impure pipe (pure: false) runs on every change detection pass, and the docs warn it can “incur a significant performance penalty.”

“Walk me through the lifecycle hooks.”

ngOnChanges runs when inputs change (the first time before ngOnInit); ngOnInit runs once, “after Angular has initialized all the components inputs”; ngAfterViewInit runs once the view and its children are created; ngOnDestroy runs before the component is removed. Modern code often uses DestroyRef for cleanup and afterNextRender or afterEveryRender for DOM work, such as measuring an element or initializing a chart library, after Angular has rendered.

“How does content projection work? viewChild vs contentChild?”

<ng-content> projects the content a parent places between your component’s tags; select creates named slots. viewChild() queries elements in your own template; contentChild() queries what was projected into you. Both have signal-based versions that return signals, so there’s no need to wait for ngAfterViewInit to read them safely.

Signals and reactivity

“What is a signal? How do computed and effect differ?”

A signal is a value that notifies anything reading it when it changes: count = signal(0), read with count(), written with set or update. A computed signal derives a value from other signals. It’s lazy and memoized: its function “does not run to calculate its value until the first time you read” it, and it reruns only when a dependency changes. Dependencies are dynamic: “Only the signals actually read during the derivation are tracked.” An effect runs side effects when the signals it reads change. “Effects always run at least once,” they run “asynchronously, during the change detection process,” and they need an injection context (or an injector passed in).

“When should you not use an effect?”

The docs are blunt: “Effects should be the last API you reach for,” and you should “avoid using effects for propagation of state changes.” Use computed() for derived values and linkedSignal() for values that are derived but can also be set by hand. Effects are for syncing signal state to things outside Angular: localStorage, logging, a third-party widget. For DOM work after rendering, use afterRenderEffect.

// Derived state: computed, not an effect
fullName = computed(() => `${this.first()} ${this.last()}`);

// Syncing to the outside world: an effect is fine
constructor() {
  effect(() => localStorage.setItem('theme', this.theme()));
}

“What is linkedSignal?”

A writable signal whose value resets when its source changes. The typical example: a selected option that defaults to the first item whenever the list of options changes, but which the user can still change.

options = input.required<string[]>();
selected = linkedSignal(() => this.options()[0]);
// this.selected.set('Express shipping') still works

“What are resource and httpResource?”

They bring asynchronous data into the signal world, and both are stable since v22. resource takes params (a reactive function) and a loader that returns a promise; whenever the params change, it reloads and exposes value(), isLoading(), error() and hasValue(). httpResource does the same for HTTP requests built from signals:

export class Weather {
  city = signal('Chicago');
  forecast = httpResource<{ temperature: number }>(
    () => `https://api.example.com/v1/forecast/${this.city()}`
  );
}

Changing city refetches automatically. Mention that untracked() reads a signal without making it a dependency, and that equality functions control when a signal counts as changed.

“Signals or RxJS?”

Use both, for what each does best. Signals hold state: a current value that templates and computations read synchronously. Observables model events over time: streams with operators for debouncing, retrying, cancelling and combining. Bridge them with toSignal (which “automatically unsubscribes” when its owner is destroyed and accepts an initialValue) and toObservable. A sound answer avoids “signals replace RxJS.”

Change detection

“How does change detection work, and what changed with zoneless?”

Angular checks components and updates the DOM when something may have changed. For years, zone.js patched browser APIs such as setTimeout and events so Angular knew when to check. Zoneless Angular drops zone.js and relies on explicit notifications: “Updating a signal that’s read in a template,” bound template or host listeners, markForCheck (which the async pipe calls) and ComponentRef.setInput. Zoneless became stable in v20.2 and “is the default in Angular v21+,” which means smaller bundles and fewer unnecessary checks. Code that changes plain fields inside setTimeout or a third-party callback, without a signal or markForCheck, won’t update the view.

“What does OnPush do, and what changed in Angular 22?”

An OnPush component is checked only when an input reference changes, an event fires in it or its children, a signal it reads changes, or it’s marked with markForCheck. Since v22, OnPush is the default for new applications, and the old default, ChangeDetectionStrategy.Default, is renamed ChangeDetectionStrategy.Eager to make clear what it does. The classic bug: mutating an object or array passed as an input (this.user.name = 'Ana') doesn’t update an OnPush child, because the reference didn’t change. Create a new object or use signals.

Dependency injection

“How does dependency injection work in Angular?”

A class asks for what it needs instead of creating it, and an injector provides it. With inject(), which “can appear in either the constructor or in a field initializer”:

@Service()
export class CartStore {
  private http = inject(HttpClient);
  items = signal<CartLine[]>([]);
}

@Service(), new in v22, is “an ergonomic shorthand for @Injectable({providedIn: 'root'})”: one app-wide instance, removed from the bundle if nothing uses it. @Injectable is still there when you need more configuration.

“Explain hierarchical injectors.”

Angular resolves a dependency by walking up from the requesting element’s injector, through its parent components, to the environment injectors (route-level, then root). Providing a service in a component’s providers gives each instance of that component its own copy; providing it in a lazy route gives that route its own scope. Use an InjectionToken to inject things that aren’t classes, such as configuration objects. Resolution modifiers like optional, self, skipSelf and host change where the lookup starts or stops.

“What is injectAsync?”

New in v22, it loads a service only when it’s first needed, so its code can be split out of the main bundle, as routes and @defer blocks already could. The service must be auto-provided, which @Service does for you:

private exporter = injectAsync(() => import('./report-exporter'));

async export() {
  const exporter = await this.exporter();
  exporter.export();
}

RxJS questions

“Observable vs Promise?”

A promise is eager, resolves once and can’t be cancelled. An observable is lazy (nothing happens until you subscribe), can emit many values, can be cancelled by unsubscribing, and has operators. HttpClient returns observables that emit once and complete.

“switchMap, mergeMap, concatMap or exhaustMap?”

All four map each value to an inner observable; they differ in what happens when a new value arrives while an inner one is still running:

  • switchMap cancels the previous inner observable. Use it for search-as-you-type, where only the latest query matters.
  • mergeMap runs them all in parallel. Use it for independent requests where order doesn’t matter.
  • concatMap queues them in order. Use it for saves that must happen one after another.
  • exhaustMap ignores new values until the current one finishes. Use it for a submit button clicked twice.

“Subject, BehaviorSubject or ReplaySubject?”

A Subject multicasts values to current subscribers only. A BehaviorSubject holds a current value and gives it to new subscribers immediately, the classic pre-signals store. A ReplaySubject replays a set number of past values. In new code, a signal often replaces a BehaviorSubject used purely for state.

“How do you avoid memory leaks with subscriptions?”

Prefer not subscribing by hand: the async pipe and toSignal unsubscribe automatically. When you must subscribe in a component, use takeUntilDestroyed(), which completes the stream when the component is destroyed. HTTP observables complete on their own, but long-lived streams such as interval, WebSocket messages, router events or a store don’t.

Routing

“How do you lazy load routes?”

export const routes: Routes = [
  { path: '', component: Home },
  {
    path: 'admin',
    canActivate: [authGuard],
    loadComponent: () => import('./admin/admin').then(m => m.Admin),
  },
  { path: 'shop', loadChildren: () => import('./shop/shop.routes').then(m => m.SHOP_ROUTES) },
];

loadComponent loads one standalone component; loadChildren loads a set of child routes. The code is downloaded only when the user navigates there.

“What are route guards?”

Guards are functions that decide whether navigation can proceed: CanActivate (enter a route), CanActivateChild, CanDeactivate (leave, for example with unsaved changes) and CanMatch, where rejection “falls through to try other matching routes.” A guard returns true, false, or a UrlTree or RedirectCommand to redirect:

export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  return auth.isLoggedIn() ? true : inject(Router).parseUrl('/login');
};

Say this in the interview: guards are a user-experience feature, not security. The docs say to “never rely on client-side guards as the sole source of access control”; the server must check permissions too.

Forms

“Template-driven, reactive or Signal Forms?”

  • Template-driven forms (ngModel) keep the logic in the template. They’re quick for simple forms and harder to test.
  • Reactive forms (FormGroup, FormControl, typed since v14) build the model in TypeScript, with observables for value changes. They’re the long-standing choice for complex forms.
  • Signal Forms, experimental in v21 and stable since v22, keep the form model in a signal and declare validation in a schema. The Angular team calls them the new, robust forms API, and Angular Material supports them.
import { Component, signal } from '@angular/core';
import { form, FormField, required, email } from '@angular/forms/signals';

@Component({
  selector: 'app-signup',
  imports: [FormField],
  template: `
    <input type="email" [formField]="f.email" />
    @if (f.email().invalid() && f.email().touched()) {
      <p>Enter a valid email.</p>
    }
    <button [disabled]="f().invalid()">Sign up</button>
  `,
})
export class Signup {
  model = signal({ email: '' });
  f = form(this.model, s => {
    required(s.email);
    email(s.email);
  });
}

HTTP

“How do you add an auth header to every request?”

With a functional interceptor, registered when you provide HttpClient:

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).token();
  return next(token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req);
};

// app.config.ts
provideHttpClient(withInterceptors([authInterceptor]))

Requests are immutable, hence clone. Interceptors are also where you handle retries, logging and 401 errors globally.

Performance and server rendering

“How do you make an Angular app faster?”

Measure first (Angular DevTools’ profiler, Lighthouse), then apply the usual fixes:

  • Render less: OnPush (now the default) and signals; a good track in @for; virtual scrolling for long lists.
  • Ship less JavaScript: lazy routes, @defer blocks and injectAsync.
  • Load images well: NgOptimizedImage (the ngSrc directive) sets priorities and sizes.
  • Avoid expensive template work: move heavy logic out of templates into computed signals or pure pipes.

“What does @defer do?”

It splits part of a template into a separate chunk that loads later. Without a trigger it loads when the browser is idle; other triggers include on viewport, on interaction, on hover, on immediate, on timer and when. Deferred dependencies must be standalone and not used elsewhere in the same file.

@defer (on viewport) {
  <app-reviews />
} @placeholder {
  <p>Customer reviews</p>
} @loading (minimum 300ms) {
  <app-spinner />
}

“What are SSR, hydration and incremental hydration?”

Server-side rendering sends real HTML, which helps first paint and search engines. Hydration reuses that HTML on the client instead of re-rendering it, and event replay (stable since v19) replays clicks made before hydration finished. Incremental hydration, stable since v20, leaves @defer blocks dehydrated until a hydrate trigger fires, so less JavaScript runs up front. By default, @defer blocks render only their @placeholder on the server.

Testing

“How do you test Angular components in 2026?”

Since v21, new projects use Vitest as the default, stable test runner (ng test); Karma was deprecated in 2023, and the Angular team provides a migration tool from Karma to Vitest. TestBed creates components with their dependencies, and in tests “TestBed uses zoneless change detection by default.” The zoneless guide advises avoiding fixture.detectChanges() when possible, so tests run change detection the way the app does in production. Component harnesses (from the CDK, and for Angular Aria since v22) make tests survive markup changes. Use real services where you can, and fakes for HTTP with provideHttpClientTesting.

Output puzzles (“what does this log?”)

Mutating a signal’s value

const items = signal(['a']);
const count = computed(() => items().length);
console.log(count());
items().push('b');
console.log(count());
items.update(list => [...list, 'c']);
console.log(count());

Answer: 1, 1, 3. push mutates the array, but the signal was never set, so count stays memoized at 1. update with a new array notifies, and count recomputes from ['a', 'b', 'c'].

When does a computed run?

const a = signal(1);
const double = computed(() => {
  console.log('computing');
  return a() * 2;
});
a.set(2);
a.set(3);
console.log(double());
console.log(double());

Answer: computing once, then 6, then 6. Nothing runs until the first read, and the second read uses the memoized value.

How many times does the effect log?

count = signal(0);
constructor() {
  effect(() => console.log('count is', this.count()));
}
increment() {
  this.count.set(1);
  this.count.set(2);
}

Answer: count is 0 on the first run, then a single count is 2 after increment(). Effects run asynchronously, so both set calls finish before the effect runs again and it sees only the latest value.

Why doesn’t the child update?

A parent passes [user]="user" to an OnPush child and later runs this.user.name = 'Ana'. Answer: the child keeps showing the old name: the input reference didn’t change, so Angular doesn’t check the OnPush child. Replace the object (this.user = { ...this.user, name: 'Ana' }) or hold it in a signal and use update.

Coding exercises

Live-coding rounds usually ask for a small feature. Practice these until you can explain each line:

  1. A search box that waits for the user to stop typing. Explain why switchMap and not mergeMap:

    results = toSignal(
      toObservable(this.query).pipe(
        debounceTime(300),
        distinctUntilChanged(),
        switchMap(q => this.http.get<Result[]>('/api/search', { params: { q } }))
      ),
      { initialValue: [] }
    );
  2. A star rating with two-way binding. A model<number>() input, a @for over five stars tracked by index (safe here, since the list never reorders), keyboard support and an aria-label on each star.

  3. Refactor from @Input and ngOnChanges to signals. Replace the inputs with input(), the derived fields with computed(), and delete ngOnChanges.

  4. A route guard and a lazy route. A CanActivateFn that redirects to /login with a UrlTree, and a loadComponent route.

  5. A custom pipe. An initials pipe ('Ada Lovelace' becomes 'AL'), and why it should stay pure.

  6. A small store. A @Service with private writable signals, public read-only versions (asReadonly()), computed totals and methods that update immutably.

For whiteboard-style algorithm questions, see our software engineer interview questions.

Questions by level

  • Junior: components and templates, bindings, @if/@for with track, pipes, services and inject(), basic routing, template-driven vs reactive forms, HttpClient.
  • Mid-level: signals (computed vs effect), OnPush and how zoneless decides when to check, RxJS flattening operators, guards and interceptors, lazy loading and @defer, testing with TestBed.
  • Senior: state design (signals, services, when to use a store library), migrating a large app to standalone, signals, OnPush and zoneless, SSR and incremental hydration, performance budgets, module boundaries in a monorepo, and how you keep a team current with a framework that now ships a major version every year.

Behavioral questions for Angular developers

Use the STAR method with a real example:

  • “Tell me about migrating an Angular app to a newer version or to standalone components. How did you roll it out?”
  • “Describe a performance problem you found in an Angular app and how you fixed it.”
  • “When did you choose RxJS over signals, or the other way round, and why?”
  • “How do you review a colleague’s component for change detection problems?”

More: our front-end developer interview questions and full-stack developer interview questions.

Frequently asked questions

What Angular questions are asked in interviews?
Components and standalone, data binding, control flow (@if, @for with track, @switch), pipes, lifecycle hooks, signals (computed, effect, linkedSignal, resource), change detection with OnPush and zoneless, dependency injection with inject(), RxJS operators and subscriptions, routing with lazy loading and guards, forms (template-driven, reactive and Signal Forms), HTTP interceptors, @defer and SSR, testing, and what changed in Angular 21 and 22.
What is the latest version of Angular?
Angular 22, released on June 3, 2026; as of early November 2026, its latest minor release is 22.2 (September 2026). Angular now plans one major release a year, so Angular 23 is expected around June 2027. Each major version is supported for 24 months.
What's new in Angular 22?
Signal Forms, Angular Aria, resource and httpResource became stable; OnPush is the default for new applications and ChangeDetectionStrategy.Default is renamed Eager; the @Service decorator and injectAsync are new; templates support arrow functions, spread syntax and multiple @case matching with exhaustive checks; and the Webpack-based builders are deprecated. Error boundaries with @boundary arrived in developer preview in 22.2.
Is zone.js still used in Angular?
Not by default. Zoneless change detection became stable in Angular 20.2, and since Angular 21 new applications don't include zone.js. Change detection runs when a signal read in a template changes, a template or host listener fires, markForCheck is called (the async pipe does this) or setInput is used. Existing apps can keep zone.js and migrate gradually.
Should I learn signals or RxJS for an Angular interview?
Both. Signals hold state that templates and computations read; RxJS handles streams of events over time, such as debounced search, retries and cancellation. Know computed vs effect, why effects shouldn't propagate state, the switchMap family of operators, and how toSignal and toObservable connect the two.
Are NgModules still used?
In existing code, yes, so you should be able to read them. For new code, the Angular team recommends standalone components and bootstrapApplication, and a component must be marked standalone: false to be declared in an NgModule.
Can Huru help me practice Angular interview questions?
Yes, for the speaking parts. You answer out loud, an AI interviewer follows up, and every answer is scored out of 100 with a stronger version. Huru doesn't run code, so practice the exercises in an editor.

Sources

Practice this with Huru AI

Turn what you just read into a rehearsal. Unlimited AI mock interviews with instant, specific feedback.

Amine Boukioud

Amine Boukioud is a co-founder of Beatview, Inc., the company behind Huru, and Head of Growth at Huru. He writes and reviews Huru’s interview guides; each one starts from primary sources such as employers’ own careers pages, official data and research, and follows Huru’s editorial policy.

Published

How this article was made: researched and drafted with AI assistance, then written, fact-checked and signed by Amine Boukioud on November 4, 2026. Sources are linked where they are used. How we work: editorial policy.