skip to content

NgModule Anatomy

The @NgModule metadata (declarations, imports, exports, providers, bootstrap), compilation scope and forRoot/forChild. Interviews still ask, since most legacy apps are built from modules.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular, what do the declarations, imports, exports, providers and bootstrap fields of an @NgModule each do?

level: juniorimportance: must knowfreq 70%

answer

  1. owns, uses, shares, injects, starts
  2. declarables belong to one module
  3. exports are the public surface
  4. templates versus DI

basics

~10 s

declarations lists the non-standalone components, directives and pipes a module owns; imports brings in what its templates use; exports shares declarables with importers; providers registers services; bootstrap names the root component bootstrapModule renders.

solid answer

~40 s

`declarations` holds the `standalone: false` components, directives and pipes that belong to the module, each declared in exactly one module. `imports` brings in other NgModules and standalone classes that the declared templates use; importing a module gives you only its `exports`. `exports` is the module's public surface: its own declarables and anything it re-exports. `providers` registers services with the injector that loads the module, which is the root injector for eagerly imported modules. `bootstrap`, used only on the root module, lists the component `bootstrapModule` renders. The first three fields govern template compilation, `providers` governs DI. New code is standalone-first; modules remain for existing code and libraries.

code

ts · 13 lines
ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserCard } from './user-card';
import { UserList } from './user-list';
import { UserApi } from './user-api';

@NgModule({
  declarations: [UserList, UserCard], // both use standalone: false
  imports: [CommonModule],            // pipes such as date for their templates
  exports: [UserCard],                // importers may use <app-user-card>
  providers: [UserApi],
})
export class UsersModule {}

go deeper

for a junior

Be able to name all five fields and say in one sentence what each holds. Know that new code is standalone and why you still meet modules.

for a middle

Separate the template-compilation fields from providers, explain that only exports are visible to importers, and that a declarable belongs to exactly one module.

for a senior

Read a legacy module and predict what its templates can resolve and which injector its providers land in, especially when the module is lazily loaded.

for a principal

Explain when keeping modules is the pragmatic choice, for example a stable library surface, versus when converting to standalone pays off for the teams that own the code.

## What an NgModule is An **NgModule** is a class marked with the `@NgModule` decorator from `@angular/core`. Its metadata tells Angular two things: how to **compile the templates** of the components it owns, and which **providers** to add to dependency injection. Before standalone components, every Angular app was assembled from NgModules. Today the Angular documentation recommends standalone components for all new code, and standalone is the default since v19 — but most production codebases still contain modules, so interviewers still ask what each field does. ```ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @NgModule({ declarations: [App, UserList, UserCard], imports: [BrowserModule, OrdersModule], exports: [UserCard], providers: [AuditLog], bootstrap: [App], }) export class AppModule {} ``` ## The five fields | Field | What goes in it | What it controls | |---|---|---| | `declarations` | Components, directives and pipes marked `standalone: false` | Which declarables **belong** to this module; each can belong to exactly one | | `imports` | Other NgModules, plus standalone components, directives and pipes | What this module's declared templates may **use** | | `exports` | Its own declarables, and anything it imports (including whole modules) | What **importing** modules' templates may use | | `providers` | Services and provider recipes | What is added to the **injector** for this module and its importers | | `bootstrap` | The root component(s), only on the root module | What `bootstrapModule` renders at startup | ### `declarations` A declarable must say `standalone: false` to be declared, because standalone is the default. Declaring the same class in two modules is a compile error, so a component shared by features lives in one module and is exported from it. ### `imports` Everything a declared template uses — another module's exported components, a pipe, a standalone directive — must reach it through `imports`. Importing a module gives you that module's **exports**, not its private declarations. ### `exports` Exports make things **visible** to importers. A module can re-export what it imports, which is how a "shared" module hands a bundle of building blocks to many feature modules. ### `providers` Providers listed here are registered with the injector that loads the module. For an eagerly imported module that is the application's root injector, so the services are app-wide singletons. For a lazily loaded module it is a separate child injector — which is why module providers and lazy loading interact badly (see `forRoot`). ### `bootstrap` Only the root module uses it. `platformBrowser().bootstrapModule(AppModule)` finds the elements in `index.html` that match the listed components' selectors and renders them. Components listed in `bootstrap` are treated as declared by that module. If the root module has neither `bootstrap` entries nor an `ngDoBootstrap` method, Angular throws `NG0403`. ## Two responsibilities, one decorator The fields fall into two groups that are easy to confuse: - **Template compilation** — `declarations`, `imports`, `exports`. These decide which selectors, attributes and pipe names a template can resolve. They have nothing to do with DI. - **Dependency injection** — `providers` (and the providers carried by imported modules). These decide what `inject()` can return. They have nothing to do with templates. Mixing the groups up produces classic mistakes: exporting a service (exports hold declarables and modules, not services), or expecting a component to become usable just because its service was provided. ## Why new code is standalone-first A standalone component lists its own template dependencies in its `imports` array. That removes the indirection of "which module declares me, and what does that module import?": - A component's dependencies are visible in its own file. - There is no shared module to keep in sync, and no risk of declaring a class twice. - Lazy loading works per component (`loadComponent`) rather than per module. Modules are not deprecated. They remain a supported way to group declarables and providers, and libraries still ship them. What changed is the default: new code starts standalone, and modules are what you read, maintain and migrate. ## Common mistakes - Putting a service in `exports` or a component in `providers`. - Declaring a standalone component instead of importing it. - Assuming `imports` gives access to a module's non-exported declarations. - Adding `bootstrap` to a feature module. - Expecting a module's `providers` to be private to that module — for an eagerly imported module they are registered in the root injector and visible app-wide. A quick way to self-check in an interview is to ask of each entry: *does this change what a template can resolve, or what `inject()` can return?* The answer tells you which field it belongs in.

  • Can a component be declared in two NgModules so both can use it?
    No. A declarable belongs to exactly one NgModule, and declaring it twice is a compile error. The fix is to declare it once, export it from that module, and import that module wherever it is needed, or to make the component standalone and import it directly.
  • Can you list a service in an NgModule's exports so importers can inject it?
    No. `exports` holds declarables and modules, which affect template compilation only. Services reach importers through `providers`: a module's providers are registered in the injector that loads it, so importers can inject them without any export.

An NgModule is like a department: declarations are its staff, exports are the staff it lends to other departments, imports are the outside staff it borrows, and providers are the shared equipment it brings into the building.

saying these in an interview costs you the question

  • exports is how you share a service with other modules
  • A component can be declared in several modules at once
  • Importing a module exposes all of its declarations, exported or not
  • Standalone components must be listed in declarations
  • NgModules are deprecated and no longer compile in Angular 22
open as a page

In a module-based Angular app, why does a template fail with 'app-user-card is not a known element', and how does NgModule compilation scope explain the fix?

level: middleimportance: must knowfreq 68%

basics

~20 s

A declared component's template can only use its own module's declarations plus what imported modules export. The error means UserCard is not in that scope: export it from its module and import that module into the one declaring the failing template.

open as a page

In Angular, why do modules such as RouterModule expose forRoot() and forChild(), and what goes wrong if a lazy-loaded module imports forRoot()?

level: middleimportance: should knowfreq 57%

basics

~20 s

forRoot() returns the module plus its singleton services and global config, imported once at the root; forChild() returns it with only feature config. A lazy module importing forRoot() re-registers services in its child injector, duplicating singletons.

open as a page

A legacy Angular SharedModule imports and re-exports forty components, three form and common modules, and provides services; what problems does it cause and how would you break it up?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Its providers are re-registered in every lazy module's injector, duplicating singletons, and its huge re-export scope hides what each template uses. Move services to providedIn: 'root' first, then split components into SCAMs or standalone components imported directly.

open as a page