Under The Hood
frontend angular Angular 14+ (standalone), 17+ (default)

Standalone Components Compilation vs NgModule Boundary

Last updated
Prerequisites:
Angular components and NgModules
Understanding of the Angular compiler (ngtsc)
  • angular
  • standalone
  • ngmodule
  • compilation
  • component
  • architecture

Read at your depth

The practical view

A standalone component declares its own dependencies directly: @Component({ standalone: true, imports: [CommonModule, MyComponent], ... }). It does not need to be declared in an NgModule. You can lazy-load it directly in routes: { path: 'x', loadComponent: () => import('./x.component').then(m => m.XComponent) }. Since Angular 17, standalone: true is the default and the recommended style. NgModules still exist for some cases (browser module config, NgModule-based libraries), but new code can be fully standalone. Remove declarations/imports from a module and use imports: [] on the component itself.

The same idea in other frameworks

react equivalent
React components are inherently standalone (import what you use); Angular's standalone mode converges to that model.

Legacy vs modern

NgModule-declared component vs standalone component

NgModule indirection requires declaring components in a module and importing the module everywhere; standalone components import dependencies directly.

before → after
NgModule-wrapped component
@Component({ selector: 'app-card', template: '...' })
@NgModule({
  declarations: [CardComponent], // ❌ OLD: must declare in a module
  imports: [CommonModule],
})
export class CardModule {}
Standalone component
@Component({
  selector: 'app-card',
  imports: [CommonModule], // ✅ NEW: dependencies declared here
  template: '...',
})
export class CardComponent {}

Interview gotchas

Context

This tests whether you understand the historical role of NgModules (compilation units, DI scope, tree-shaking boundaries) and how standalone components move that responsibility.

The mechanical answer

NgModules were the compilation and dependency-scoping unit: a component could only use directives/pipes listed in its module's imports, which defined both the compilation unit (what the compiler knows about) and the injector scope. This enabled ahead-of-time compilation with a bounded dependency set, but it also meant indirection (declare + import + export), harder tree-shaking, and heavier lazy chunks (a module loads all its declarations). Standalone components declare dependencies directly in imports: [], so the compiler resolves the exact dependency graph per component, enabling precise tree-shaking and component-level lazy loading. The DI scope still comes from the injector (environment + element), but there is no artificial module boundary to configure.

Trap

The naive answer is 'standalone just removes a file'. It changes compilation granularity and tree-shaking. Another trap: assuming NgModules are dead — existing libraries and some setup (BrowserModule, providers arrays) still use them; migration is mechanical but not zero. Candidates who mention compilation units, DI scope, and lazy-chunk size show real architectural understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.