Standalone components remove the need for NgModules for most app composition, routing, and DI setup. NgModules still solve a few practical problems: packaging legacy (non-standalone) declarations, consuming module-based third-party libraries (forRoot/forChild), and providing a single import/export “bundle” boundary that some teams and libraries still rely on. Covers: angular, modules, standalone, architecture, basics.
What problems do NgModules solve that standalone components don’t?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Overview
Standalone components shift Angular’s "template dependency scope" from @NgModule to the component itself (imports on the component). For modern apps, that removes most reasons to create feature/shared modules.
NgModules still matter mainly as a packaging + compatibility layer for legacy declarables and module-based libraries/config APIs.
Problem / need | NgModule solves it by... | Standalone status |
|---|---|---|
Using non-standalone directives/pipes/components (legacy code or older libs) | Declaring them once and exporting them via a module | Standalone cannot directly import non-standalone declarables; you import the NgModule that exports them. |
Module-style library configuration (forRoot/forChild patterns) | Returning | Standalone supports this mostly via |
Single “bundle import” for a large set of template dependencies | One | Standalone can mimic with a shared array/const, but there’s no first-class “export scope” container like NgModule exports. |
Gradual migration in an existing NgModule app | You can convert leaf components to standalone while keeping module boundaries intact | Standalone is great for incremental migration, but NgModules remain the glue during the transition. |
Team convention: explicit public API surface (exports) for a domain | Exports define what the rest of the app can use from that domain | Standalone relies on regular TS exports + per-component imports; you lose the explicit “exports list” mechanism. |
// legacy-shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LegacyHighlightDirective } from './legacy-highlight.directive';
import { LegacyDatePipe } from './legacy-date.pipe';
@NgModule({
declarations: [LegacyHighlightDirective, LegacyDatePipe],
imports: [CommonModule],
exports: [CommonModule, LegacyHighlightDirective, LegacyDatePipe]
})
export class LegacySharedModule {}
// standalone component consuming legacy declarations via NgModule
import { Component } from '@angular/core';
import { LegacySharedModule } from './legacy-shared.module';
@Component({
standalone: true,
selector: 'app-user-card',
imports: [LegacySharedModule],
template: `
<div legacyHighlight>
{{ today | legacyDate }}
</div>
`
})
export class UserCardComponent {
today = new Date();
}
If you’re fully standalone, do this instead | Why it’s the equivalent |
|---|---|
Create reusable import bundles as constants (not modules) | Gives you “one name” to import across many standalone components without an NgModule exports list. |
Prefer provider functions ( | Moves app configuration to bootstrap/route providers and reduces NgModule-only configuration patterns. |
// shared-imports.ts (standalone-friendly)
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
export const SHARED_IMPORTS = [CommonModule, RouterModule] as const;
// any.component.ts
import { Component } from '@angular/core';
import { SHARED_IMPORTS } from './shared-imports';
@Component({
standalone: true,
imports: [...SHARED_IMPORTS],
template: `...`
})
export class AnyComponent {}
// Module-based library config still shows up a lot
import { bootstrapApplication, importProvidersFrom } from '@angular/core';
import { AppComponent } from './app.component';
import { TranslateModule } from 'some-translate-lib';
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(TranslateModule.forRoot({ defaultLang: 'en' }))
]
});
Practical notes
Watch for edge case behavior, common pitfalls, and trade-offs between clarity and performance. Mention accessibility and testing considerations when the concept affects UI output or event timing.
Standalone components cover most of what NgModules were used for (composition, routing, DI setup). NgModules still “win” mainly for legacy/non-standalone declarations and module-shaped third-party APIs (especially forRoot/forChild). In a greenfield app with modern libs, you usually don’t need to write new NgModules.