Angular interviews test your understanding of its component model, dependency injection, and change detection — the parts that differ most from other frontend frameworks.
A component is the fundamental building block of an Angular UI, made up of a TypeScript class decorated with `@Component`, an HTML template, and optionally its own CSS. The decorator's metadata tells Angular which selector to match in the DOM, which template or templateUrl to render, and which styles to scope to it. The class itself holds state and behavior, exposing properties and methods that the template binds to. Every Angular application has at least one root component that bootstraps the app, and everything you see on screen is really a tree of nested components. Compared to older MVC patterns, components bundle view and logic together, which makes them easy to test and reuse in isolation.
An NgModule is a class decorated with `@NgModule` that groups related components, directives, pipes, and services into a cohesive unit, telling Angular's compiler how to build that chunk of the application. Its metadata includes declarations for the components and directives it owns, imports for other modules it depends on, providers for services, and bootstrap for the root component to launch. Historically every app needed at least a root `AppModule`, and larger apps split features into their own modules for lazy loading and better organization. Modules also help Angular's compiler do ahead-of-time compilation efficiently by knowing exactly what belongs together. With Angular's move toward standalone components, NgModules are no longer mandatory, but they're still common in existing codebases and worth understanding well.
Angular supports four main kinds of data binding, and interviewers usually want you to name and briefly demonstrate each. Interpolation uses double curly braces like `{{ value }}` to insert a component property's value into the template as text. Property binding, written as `[property]="value"`, sets a DOM element or component property from the component class, flowing data one way from class to view. Event binding, written as `(event)="handler()"`, listens for DOM or custom events and runs a method on the component when they fire, flowing from view to class. Two-way binding combines both directions using the banana-in-a-box syntax `[(ngModel)]="value"`, so the view updates when the model changes and the model updates when the user interacts with it.
Two-way data binding keeps a component property and a form element's value in sync in both directions, so a change in either place is immediately reflected in the other. Angular achieves this with the `[(ngModel)]` syntax, which is really syntactic sugar combining a property binding `[ngModel]` and an event binding `(ngModelChange)` under the hood. The directive listens for input events on the element, updates the bound property, and emits `ngModelChange`, while also writing the property's value back into the element whenever it changes elsewhere. To use `ngModel` you need to import `FormsModule`, since it comes from Angular's forms package rather than core. It's most common in template-driven forms, whereas reactive forms typically bind through `formControlName` instead.
Directives are classes that add behavior to DOM elements, and Angular splits them into structural and attribute categories based on what they change. Structural directives, like `*ngIf`, `*ngFor`, and `*ngSwitch`, alter the structure of the DOM by adding, removing, or repeating elements, and they're prefixed with an asterisk, which is shorthand for Angular's `ng-template` syntax. Attribute directives, like `ngClass`, `ngStyle`, or a custom highlight directive, change the appearance or behavior of an existing element without adding or removing anything from the DOM. Under the hood, a structural directive typically works with `TemplateRef` and `ViewContainerRef` to instantiate or destroy embedded views, while an attribute directive just manipulates the host element's properties or listens to its events. You can only apply one structural directive per host element directly, which is why you'll often see `ng-container` used to combine `*ngIf` and `*ngFor` on the same logical element.
Dependency injection is a design pattern where a class receives its dependencies from an external source rather than creating them itself, and Angular has DI built into its core rather than bolted on. A service is typically just a plain class decorated with `@Injectable`, used to hold logic like API calls or shared state that doesn't belong in a component. When you declare a dependency in a component's constructor, Angular's injector resolves it by looking up a provider, often registered via `providedIn: 'root'`, which makes the service a singleton available application-wide. Angular's injectors are hierarchical, so you can also provide a service at the module, route, or component level to scope its lifetime and get a fresh instance for that subtree. This pattern keeps components leaner and much easier to unit test, since you can swap in mock services instead of relying on real implementations.
Change detection is the process Angular uses to figure out when component data has changed and the view needs updating, and by default it works by checking every component in the tree from top to bottom whenever something could have changed. Zone.js is a library Angular patches into the browser's asynchronous APIs, like `setTimeout`, promises, and DOM events, so it can automatically detect when an async operation has finished and trigger a change detection cycle afterward. This is what lets you update a property in a callback and see the template refresh without manually telling Angular to check anything. The default strategy is sometimes called "CheckAlways" because every component gets checked on every cycle, which is simple but can get expensive in large applications. Newer Angular versions are moving toward zoneless change detection using signals, but Zone.js is still the mechanism most existing apps rely on.
`ChangeDetectionStrategy.OnPush` tells Angular to skip checking a component during a change detection cycle unless one of a few specific triggers occurs, which can dramatically improve performance in larger apps. Those triggers are an `@Input` reference changing, an event originating from within the component or its children, an `async` pipe emitting a new value, or manually calling `markForCheck` on the `ChangeDetectorRef`. The key catch is that Angular checks input properties by reference, not by deep equality, so mutating an object or array in place won't trigger a check under OnPush, you need to pass a new reference instead. It works especially well when components rely on immutable data or observables rather than mutating shared state directly. You set it in the component decorator with `changeDetection: ChangeDetectionStrategy.OnPush`, and it's one of the first things to reach for when profiling shows change detection as a bottleneck.
Angular components go through a defined lifecycle from creation to destruction, and lifecycle hooks let you tap into specific moments of that process. `ngOnChanges` fires whenever an `@Input` property's reference changes, running before `ngOnInit` and on every subsequent input change, and it receives a `SimpleChanges` object with the previous and current values. `ngOnInit` runs once, right after Angular has set the initial input properties, and it's the standard place to do initialization work like fetching data, since the constructor should stay lean and free of side effects. `ngOnDestroy` runs just before Angular removes the component, and it's essential for cleanup like unsubscribing from observables, clearing intervals, or detaching event listeners to avoid memory leaks. There are other hooks too, like `ngAfterViewInit` and `ngAfterContentInit`, but these three come up in interviews the most because they map directly to setup and teardown responsibilities.
An Observable is a core RxJS concept representing a stream of values over time that you subscribe to, and Angular uses it pervasively for anything asynchronous. `HttpClient` returns observables for every request, the `Router` exposes route parameters and query params as observables, and reactive forms expose `valueChanges` and `statusChanges` as observables too. You subscribe to an observable to start receiving its emitted values, and RxJS operators like `map`, `filter`, `switchMap`, and `debounceTime` let you transform or combine those streams declaratively. Because subscriptions stay live until they complete or you unsubscribe, forgetting to clean them up is a classic source of memory leaks, which is why the `async` pipe is preferred in templates since it subscribes and unsubscribes automatically. Overall, observables give Angular a consistent, composable way to handle things like HTTP calls, user input, and real-time data.
A Promise represents a single asynchronous value that resolves or rejects exactly once and starts executing as soon as it's created, whereas an Observable represents a stream that can emit zero, one, or many values over time and doesn't do anything until you subscribe to it. Because observables are lazy, the same observable can be subscribed to multiple times, potentially re-triggering the underlying work, which gives you more control but also more to think about. Observables also come with a rich set of operators for transforming, combining, and cancelling streams, like `switchMap` or `takeUntil`, whereas promises only offer `then`, `catch`, and `finally`. Cancellation is another big difference: you can unsubscribe from an observable to stop it, but a promise, once created, will always settle. In practice Angular uses observables for anything that can happen more than once or needs to be cancelled, like HTTP requests you might abandon, while promises still show up for simpler one-off async code using `async`/`await`.
The Angular Router maps URL paths to components using a route configuration array, where each route entry specifies a path and the component, lazy-loaded module, or standalone component to render inside a `router-outlet`. It supports route parameters, query parameters, nested and lazy-loaded routes, and named outlets for more complex layouts. Route guards are interfaces or functions you implement to control navigation, and the most common ones are `CanActivate`, which decides whether a route can be entered, `CanDeactivate`, which asks whether you can leave a route, and `CanMatch` (formerly `CanLoad`), which can block a lazy-loaded route from even being matched. A typical use case is an authentication guard that checks whether a user is logged in and redirects to a login page if not, returning `true`, `false`, or a `UrlTree`. In recent Angular versions, guards are increasingly written as plain functions using `CanActivateFn` rather than injectable classes, which is simpler for straightforward checks.
AngularJS, sometimes called Angular 1, is the original framework built around controllers, `$scope`, and two-way binding driven by digest cycles, written entirely in JavaScript. Angular, from version 2 onward, is a complete rewrite built on TypeScript, using a component-based architecture instead of controllers and scope, and a much faster change detection model instead of digest cycles. Angular also introduced a proper module system, dependency injection redesigned around hierarchical injectors, and tooling like the Angular CLI and Ivy compiler for better build performance and smaller bundles. Mobile support and raw performance were major goals of the rewrite, since AngularJS wasn't designed with mobile devices or server-side rendering in mind. AngularJS is now deprecated and out of long-term support, so essentially all new development targets modern Angular, and versioning after Angular 2 just increments normally, like Angular 17 or 18, rather than restarting.
Standalone components are components, directives, or pipes marked with `standalone: true` that declare their own dependencies directly through an `imports` array, removing the requirement to declare them inside an NgModule. This simplifies the mental model considerably, since you no longer need a matching module just to make a component usable, and it cuts down on a lot of boilerplate in smaller or newer applications. Bootstrapping can also be done without a root module, using `bootstrapApplication` with a root standalone component, and routing can be configured with plain functions like `provideRouter` instead of `RouterModule.forRoot`. Standalone components work fine alongside existing NgModule-based code, so teams can migrate incrementally rather than rewriting everything at once. As of recent Angular versions, standalone is actually the default for newly generated components via the CLI, reflecting where the framework is heading.
Template-driven forms build the form structure mostly in the HTML template using directives like `ngModel` and `ngForm`, with Angular inferring the form model behind the scenes, which makes them quick to set up for simple forms. Reactive forms, by contrast, define the form model explicitly in the component class using `FormGroup`, `FormControl`, and `FormArray`, and the template just binds to that existing structure with directives like `formGroup` and `formControlName`. Reactive forms are generally preferred for anything moderately complex because the form logic is testable in isolation, you get synchronous access to form state, and validation is composed programmatically with validator functions rather than template attributes. Template-driven forms rely more heavily on two-way binding and are asynchronous under the hood, which can make advanced scenarios like dynamic validation or dynamically added fields more awkward. Both approaches share the same underlying `AbstractControl` based validation model, so concepts like validators and error states carry over between the two.
A pipe is a simple class decorated with `@Pipe` that transforms a value in the template without you having to write that transformation logic in the component, used with the `|` syntax like `{{ birthday | date }}`. Angular ships several built-in pipes such as `date`, `currency`, `uppercase`, and `async`, and you can also write custom pipes by implementing the `PipeTransform` interface's `transform` method. By default pipes are pure, meaning Angular only re-runs them when the input reference changes, which is efficient but can miss changes to objects or arrays mutated in place. Marking a pipe with `pure: false` makes it impure, so it re-evaluates on every change detection cycle regardless of whether the reference changed, which is more flexible but noticeably more expensive if overused. The `async` pipe is a special built-in impure pipe worth knowing well, since it subscribes to an observable or promise directly in the template and automatically unsubscribes when the component is destroyed.
Content projection is how Angular lets a parent component pass markup into a child component's template, so the child renders content it doesn't own or define itself, similar in spirit to a slot system. You mark the insertion point in the child's template with `<ng-content></ng-content>`, and anything nested inside the child's tag in the parent's template gets projected there at render time. For more complex layouts you can use multiple `ng-content` outlets with the `select` attribute to project different pieces of content into different slots based on a CSS selector, like `select="[header]"`. This is commonly used for reusable wrapper components, like a card or a modal, where the shell defines structure and styling while the actual content varies per use. Unlike content generated by `*ngIf` inside the child, projected content is compiled in the parent's context, so it still has access to the parent component's properties and methods.