Angular Fundamentals Questions
Angular interviews almost always start with the fundamentals: what a component is, how data flows through the template, how the framework is structured, and why modern Angular has moved toward standalone components and signals. This page walks through the questions you are most likely to be asked, with precise, modern answers using Angular 17/18/19 APIs. Knowing these cold lets you spend the rest of the interview on the harder problems.
What is Angular, and how does it differ from AngularJS?
Angular is a TypeScript-based framework for building single-page applications. It is a complete platform — it ships its own router, HTTP client, forms, dependency injection, and a CLI. AngularJS (1.x) was a different, JavaScript-based framework that used $scope, two-way digest-cycle binding, and controllers; modern Angular (2+) is a full rewrite with a component tree, a compiler, ahead-of-time (AOT) compilation, and a unidirectional data flow.
The most important practical differences interviewers look for: Angular is component-based rather than controller/scope-based, it uses TypeScript and decorators, it compiles templates ahead of time, and recent versions favor standalone components over NgModules.
What is a component?
A component is the basic UI building block. It pairs a TypeScript class (logic and state) with an HTML template (view) and optional styles. The @Component decorator wires them together. In modern Angular, components are standalone by default, so they declare their own dependencies in imports.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<button (click)="increment()">Clicked {{ count() }} times</button>
`,
})
export class CounterComponent {
count = signal(0);
increment(): void {
this.count.update((n) => n + 1);
}
}
In Angular 19,
standalone: trueis the default and can be omitted. You only writestandalone: falsefor components that still belong to an NgModule.
What is data binding, and what types exist?
Data binding keeps the component class and the template in sync. Angular offers four forms, distinguished by the direction of data flow and the syntax.
| Type | Syntax | Direction |
|---|---|---|
| Interpolation | {{ value }} | Class → View |
| Property binding | [prop]="value" | Class → View |
| Event binding | (event)="handler()" | View → Class |
| Two-way binding | [(ngModel)]="value" | Both |
Two-way binding is syntactic sugar: [(x)]="y" expands to [x]="y" (xChange)="y = $event". The model() signal input introduced in Angular 17.2 makes a component support [(value)] natively.
import { Component, model } from '@angular/core';
@Component({
selector: 'app-toggle',
standalone: true,
template: `<button (click)="checked.set(!checked())">{{ checked() }}</button>`,
})
export class ToggleComponent {
checked = model(false); // enables <app-toggle [(checked)]="..." />
}
What is the difference between a module and a standalone component?
An NgModule (@NgModule) is a container that groups components, directives, pipes, and providers and declares which are exported. For years every app needed at least an AppModule. Standalone components removed that requirement: a component declares its own template dependencies directly in imports, so you can build an entire app without writing a single NgModule.
// Bootstrapping a standalone app — no AppModule needed
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
Standalone is now the recommended approach. NgModules still exist for backward compatibility and for grouping providers via provide* functions.
What is the new control flow syntax?
Angular 17 introduced built-in control flow blocks (@if, @for, @switch) that replace the structural directives *ngIf, *ngFor, and *ngSwitch. They are built into the compiler — no imports needed — and are faster and easier to read. @for requires a track expression for efficient list diffing.
@if (user(); as u) {
<p>Welcome, {{ u.name }}</p>
} @else {
<p>Please sign in.</p>
}
@for (item of items(); track item.id) {
<li>{{ item.label }}</li>
} @empty {
<li>No items yet.</li>
}
How does dependency injection work?
Angular has a hierarchical dependency injection system. Services are registered as providers, and Angular creates and supplies their instances where requested. The modern, recommended way to obtain a dependency is the inject() function, which works in field initializers and replaces most constructor injection.
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
getUsers() {
return this.http.get<User[]>('/api/users');
}
}
providedIn: 'root' makes the service a singleton available app-wide and tree-shakable — if nothing injects it, it is dropped from the bundle.
How do components communicate?
The standard pattern is input() for passing data down and output() for emitting events up. The signal-based input()/output() functions (Angular 17.1+) replaced the older @Input()/@Output() decorators.
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-product',
standalone: true,
template: `
<h3>{{ name() }}</h3>
<button (click)="buy.emit(name())">Buy</button>
`,
})
export class ProductComponent {
name = input.required<string>(); // parent: [name]="..."
buy = output<string>(); // parent: (buy)="onBuy($event)"
}
For unrelated components, share state through an injectable service holding signals or observables.
What is the component lifecycle?
Angular calls lifecycle hooks at defined moments. The most common are ngOnInit (after the first inputs are set — do setup here, not in the constructor), ngOnChanges (when an @Input changes), ngOnDestroy (cleanup — unsubscribe, clear timers), and ngAfterViewInit (after the view and child views render). With signals, much init-time work moves to field initializers and effect(), reducing reliance on ngOnInit.
Best Practices
- Prefer standalone components for all new code; reserve NgModules for legacy interop or grouping providers.
- Use the new
@if/@for/@switchcontrol flow and always supply atrackfor@for. - Reach for signals (
signal,computed,input,output,model) instead of decorators and manual change detection. - Use
inject()over constructor injection for cleaner, more composable code. - Register app-wide services with
providedIn: 'root'so they stay singletons and tree-shakable. - Keep templates declarative — push logic into the class or a service, not into the template.
- Always clean up subscriptions and timers in
ngOnDestroy, or usetakeUntilDestroyed()to avoid leaks.