Frontend / Angular / 01_angular_essentials.md

Angular essentials

Updated 7 interview angles 4 min read source
On this page6
  1. The shape of it
  2. Dependency injection
  3. Change detection, and why signals arrived
  4. RxJS: the part people struggle with
  5. Related
  6. Interview angle

Angular essentials

Angular is the third framework interviewers name alongside React and Vue, and it is the one most often described from a 2018 memory. Modern Angular - standalone components, signals, the new control flow - is a different framework from the one with NgModules everywhere.

The shape of it

Angular is a framework, not a library: routing, HTTP, forms, testing and dependency injection ship with it and are expected to be used. That is the central difference from React, where each of those is a choice.

A component is a class with a decorator:

ts
@Component({
  selector: "app-invoice",
  standalone: true,
  template: `<h2>{{ total() | currency }}</h2>`,
})
export class InvoiceComponent {
  total = signal(0);
}

Three things are visible here that define the model: TypeScript is mandatory rather than optional, the template is a separate language with its own syntax, and standalone: true is the modern default.

Standalone components

Angular’s original unit of organisation was the NgModule - a declaration block listing every component, directive and pipe available to a template.

Standalone components, stable since Angular 15 and the default in new projects from 16 onward, removed that. A component declares its own imports:

ts
@Component({
  standalone: true,
  imports: [CommonModule, RouterLink],
  template: `<a routerLink="/home">Home</a>`,
})

If asked about NgModules, the useful answer is that they were the old dependency-declaration mechanism and are no longer how new code is written.

Dependency injection

This is Angular’s most distinctive feature and the one a Python backend engineer will recognise immediately: a hierarchical DI container, the same idea as FastAPI’s Depends or a service container.

ts
@Injectable({ providedIn: "root" })
export class InvoiceApi {
  private http = inject(HttpClient);

  list() {
    return this.http.get<Invoice[]>("/api/invoices");
  }
}

providedIn: "root" makes it a singleton for the application. Providing it at a component instead gives that component subtree its own instance, which is how scoping works.

The payoff is the same as on the backend: swapping the real API for a fake in a test is a provider override, not a module mock. See Dependency Injection.

Change detection, and why signals arrived

Classic Angular used zone.js, which monkey-patches browser async APIs to know when something might have changed, then checks the component tree. It works without any annotation from you, and it over-checks: any timer or event triggers a pass over components that did not change.

OnPush narrowed that by only re-checking when inputs change by reference.

Signals replaced the problem rather than tuning it. A signal is a reactive value that records who read it, so a write can notify exactly those consumers.

ts
count = signal(0);
double = computed(() => this.count() * 2);

increment() {
  this.count.update(n => n + 1);
}

Reading is a function call - count() - which is what lets Angular track the dependency. computed is derived and cached; it recomputes only when something it read has changed.

This is the same model as Vue’s reactivity and Solid’s signals, and it is moving Angular toward zoneless applications where change detection is driven by signal writes rather than by patched async APIs.

The new control flow

Templates gained built-in block syntax, replacing the structural directives:

html
@if (user(); as u) {
  <p>{{ u.name }}</p>
} @else {
  <p>Signed out</p>
}

@for (item of items(); track item.id) {
  <li>{{ item.name }}</li>
}

track is required in @for and is the equivalent of React’s key - without a stable identity the list is re-created rather than moved.

RxJS: the part people struggle with

Angular’s HTTP client and router return Observables, so RxJS is not optional in a real codebase. An Observable is a stream you compose with operators.

ts
search = this.query.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(q => this.api.search(q)),
);

switchMap is worth knowing by name: it cancels the previous inner request when a new value arrives, which is exactly right for a search box and is the canonical example of why streams beat promises for this problem.

The current direction is signals for component state and RxJS for genuine event streams, with toSignal bridging the two. Saying that split shows you have used recent Angular.

Interview angle 7

  • “How is Angular different from React?” - it is a framework rather than a library: routing, HTTP, forms, testing and dependency injection are included and expected, TypeScript is mandatory, and templates are a separate language. React leaves each of those to you.
  • “What replaced NgModules?” - standalone components, stable from Angular 15 and the default since 16. A component declares its own imports instead of being registered in a module’s declarations block.
  • “How does Angular’s DI work?” - a hierarchical injector. providedIn: "root" gives an application singleton; providing at a component scopes an instance to that subtree. Tests override providers rather than mocking modules.
  • “What was wrong with zone.js?” - it patches async browser APIs to detect possible changes, then checks the component tree, so unrelated timers and events trigger checks of components that did not change. OnPush narrows it; signals remove the need for it.
  • “What is a signal?” - a reactive value that tracks its readers, so a write notifies exactly the consumers that read it. Reading is a call, count(), which is how the dependency is recorded, and computed derives cached values. It is the same model as Vue’s reactivity and enables zoneless change detection.
  • “Why is track required in @for?” - it gives each row a stable identity so the DOM node is moved rather than rebuilt, the same role as React’s key.
  • “Where does RxJS still belong?” - genuine event streams and request cancellation, where switchMap discards an in-flight request when a newer value arrives. Component state is moving to signals, with toSignal bridging.