DEV Community

Atilla Baspinar
Atilla Baspinar

Posted on

Angular Modules


1. What is an NgModule

An NgModule groups related components, directives, and pipes together. It was the primary way to structure Angular apps before standalone components arrived in Angular 14.

NgModules are not deprecated — they are still fully supported in Angular 21. Only platformBrowserDynamic (the old bootstrap function) was deprecated. The recommended path for new projects is standalone components, but NgModules remain valid for existing codebases.


2. NgModule Anatomy

The @NgModule decorator accepts five main properties:

Property Purpose
declarations Non-standalone components, directives, and pipes owned by this module
imports Other NgModules or standalone components/pipes this module needs
exports What this module exposes to other modules that import it
providers Services to register (prefer providedIn: 'root' on the service itself)
bootstrap The root component Angular renders on startup — root module only
@NgModule({
  declarations: [App],
  bootstrap: [App],
  imports: [BrowserModule, CommonModule, Tasks, NewTask, Task, User, Header, RouterOutlet],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

3. declarations vs imports

This is the most common source of confusion when mixing modules with standalone components.

  • declarations — only for non-standalone components (standalone: false). They are private to this module unless explicitly exported.
  • imports — for other NgModules and for standalone components, directives, and pipes (the default since Angular 17).

Putting a standalone component in declarations causes a compile error. Putting a non-standalone component in imports also causes an error.

// non-standalone component — belongs in declarations
@Component({
  standalone: false,
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {}
Enter fullscreen mode Exit fullscreen mode
// standalone component (Angular 17+ default) — belongs in imports, not declarations
@Component({
  selector: 'app-header',
  templateUrl: './header.html',
})
export class Header {}
Enter fullscreen mode Exit fullscreen mode
@NgModule({
  declarations: [App],                          // non-standalone
  imports: [BrowserModule, Header, RouterOutlet], // standalone components go here
  bootstrap: [App],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

A non-standalone component also cannot have an imports array in @Component — it delegates all imports to its module.


4. BrowserModule vs CommonModule

Module Use when
BrowserModule Root module only — sets up the browser platform, includes CommonModule internally
CommonModule Feature modules — provides NgIf, NgFor, NgClass, pipes, etc.

Never import BrowserModule in a feature module — import CommonModule instead. Importing BrowserModule twice throws a runtime error.


5. Bootstrapping with NgModule

Use platformBrowser().bootstrapModule() from @angular/platform-browser. The older platformBrowserDynamic from @angular/platform-browser-dynamic was deprecated in Angular 21.

// main.ts
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app.module';

platformBrowser().bootstrapModule(AppModule)
  .catch((err: unknown) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

6. Feature Modules

Feature modules group a slice of the app (e.g. tasks, users) to keep AppModule small. They export only what the rest of the app needs.

// tasks/tasks.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TasksComponent } from './tasks.component';

@NgModule({
  declarations: [TasksComponent],
  imports: [CommonModule],
  exports: [TasksComponent],
})
export class TasksModule {}
Enter fullscreen mode Exit fullscreen mode
// app.module.ts
@NgModule({
  declarations: [App],
  imports: [BrowserModule, TasksModule],
  bootstrap: [App],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

7. Lazy-Loaded Feature Modules

Lazy loading defers loading a module's bundle until the user navigates to its route. Configure it with loadChildren in the router:

// app.routes.ts
export const routes: Routes = [
  {
    path: 'tasks',
    loadChildren: () =>
      import('./tasks/tasks.module').then((m) => m.TasksModule),
  },
];
Enter fullscreen mode Exit fullscreen mode
// tasks/tasks.module.ts — use forChild, not forRoot
@NgModule({
  declarations: [TasksComponent],
  imports: [
    CommonModule,
    RouterModule.forChild([{ path: '', component: TasksComponent }]),
  ],
})
export class TasksModule {}
Enter fullscreen mode Exit fullscreen mode

The root module uses RouterModule.forRoot(routes). Feature modules use RouterModule.forChild(routes).


8. NgModule vs Standalone

NgModule Standalone
Grouping Explicit module files No module needed
Sharing Export from module, import the module Import the component directly
Lazy loading loadChildren loadComponent
Recommended for new projects No Yes
Deprecated No

NgModules are still valid and fully supported. Understanding them is essential for working with existing Angular codebases.

Top comments (0)