What problems do NgModules solve that standalone components don’t?

LowIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

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.

Answer

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 ModuleWithProviders and wiring providers through module imports

Standalone supports this mostly via importProvidersFrom(SomeModule.forRoot(...)), but the library still forces the NgModule shape.

Single “bundle import” for a large set of template dependencies

One SharedModule re-exports many imports/exports so feature code imports one thing

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.

Where NgModules still provide value in a standalone-first Angular world
TYPESCRIPT
// 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 {}
                  
TYPESCRIPT
// 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 (provideRouter, provideHttpClient, etc.)

Moves app configuration to bootstrap/route providers and reduces NgModule-only configuration patterns.

Standalone alternatives to common “SharedModule” patterns
TYPESCRIPT
// 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 {}
                  
TYPESCRIPT
// 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.

Summary

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.

Similar questions
Guides
35 / 37