AngularJSModules vs Standalone

Angular Modules vs Standalone Components

Angular has two ways to organise and declare components, directives, and pipes: the classic NgModule system and the newer Standalone architecture introduced in Angular 14 and made the default in Angular 17.

Understanding both is essential — you will encounter NgModules in existing codebases, and you need Standalone for greenfield projects.

What Is an NgModule?

An NgModule is a class decorated with @NgModule. It acts as a compilation context: it tells Angular which components, directives, and pipes belong together, what they import, and what they export for other modules to use.

Every classic Angular app has at least one module — AppModule — which bootstraps the root component.

TS
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { UserCardComponent } from './user-card/user-card.component';
import { SharedModule } from './shared/shared.module';

@NgModule({
  declarations: [
    AppComponent,       // components that belong to this module
    UserCardComponent,
  ],
  imports: [
    BrowserModule,      // built-in Angular modules
    SharedModule,       // other feature modules
  ],
  exports: [
    UserCardComponent,  // expose for use in other modules
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
Note
In NgModule architecture every component must be declared in exactly one module. Forgetting to declare a component — or importing the wrong module — is a very common beginner mistake.
What Is a Standalone Component?

A standalone component sets standalone: true inside its @Component decorator and manages its own imports directly — no NgModule needed.

Standalone components, directives, and pipes can import other standalone pieces or entire NgModules, giving you fine-grained control over dependencies.

TS
// user-card.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';

@Component({
  standalone: true,                     // key flag
  selector: 'app-user-card',
  imports: [CommonModule, RouterLink],  // direct imports — no module wrapper
  template: `
    <div class="card">
      <h3>{{ user.name }}</h3>
      <a [routerLink]="['/users', user.id]">View Profile</a>
    </div>
  `,
})
export class UserCardComponent {
  user = { id: 1, name: 'Alice' };
}
Bootstrapping a Standalone Application

With standalone components you bootstrap using bootstrapApplication instead of the NgModule-based platformBrowserDynamic().bootstrapModule().

TS
// main.ts — standalone bootstrap
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
  ],
}).catch(err => console.error(err));

TS
// app.component.ts — standalone root component
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  standalone: true,
  selector: 'app-root',
  imports: [RouterOutlet],
  template: `<router-outlet />`,
})
export class AppComponent {}
NgModule Feature Modules

In NgModule architecture, features are grouped into feature modules which are then lazy-loaded or eagerly imported by AppModule.

TS
// products/products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductsRoutingModule } from './products-routing.module';
import { ProductListComponent } from './product-list/product-list.component';

@NgModule({
  declarations: [ProductListComponent],
  imports: [CommonModule, ProductsRoutingModule],
})
export class ProductsModule {}

// products/products-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';

const routes: Routes = [
  { path: '', component: ProductListComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class ProductsRoutingModule {}
Standalone Routes (No NgModule)

With standalone components, route configuration lives in a plain TypeScript file — no routing module class required.

TS
// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'products',
    loadComponent: () =>
      import('./products/product-list.component')
        .then(m => m.ProductListComponent),
  },
  {
    path: 'products/:id',
    loadComponent: () =>
      import('./products/product-detail.component')
        .then(m => m.ProductDetailComponent),
  },
  { path: '', redirectTo: 'products', pathMatch: 'full' },
];
Side-by-Side Comparison

Aspect

NgModule

Standalone

Declaration

Declared in @NgModule

standalone: true in @Component

Imports

Module-level imports array

Component-level imports array

Bootstrapping

bootstrapModule(AppModule)

bootstrapApplication(AppComponent)

Lazy loading

loadChildren with a module

loadComponent with a component

Boilerplate

High (extra module files)

Low (no extra files)

Tree-shaking

Module-level granularity

Component-level (better)

Default since

Angular 2–16

Angular 17+

Mixing NgModules and Standalone

You can use standalone components inside NgModule apps and vice-versa. This makes incremental migration straightforward.

TS
// Import a standalone component into an NgModule
@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StandaloneButtonComponent, // standalone imported directly into NgModule
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

// Import an NgModule into a standalone component
@Component({
  standalone: true,
  imports: [
    ReactiveFormsModule,  // NgModule used inside a standalone component
    CommonModule,
  ],
  template: `...`,
})
export class MyFormComponent {}
Provider Scoping

Both architectures support hierarchical dependency injection. The key difference is where you register providers.

TS
// NgModule: provide at module level
@NgModule({
  providers: [ProductService],
})
export class ProductsModule {}

// Standalone: provide in bootstrapApplication
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    ProductService,
  ],
});

// Best practice: use providedIn: 'root' for tree-shakeable singletons
// Works for both NgModule and standalone apps
@Injectable({ providedIn: 'root' })
export class ProductService {
  // available everywhere, tree-shaken when unused
}
Warning
Avoid registering the same service in both an NgModule providers array and a standalone provider array — you will get two separate instances, which causes hard-to-debug state inconsistencies.
When to Use Which
  • New projects (Angular 17+): always use standalone — it is the default and recommended approach

  • Existing NgModule codebases: keep using NgModules unless you have a dedicated migration window

  • Libraries: standalone components are more composable and tree-shakeable for consumers

  • Migration path: convert leaf components to standalone first, then work upward toward the root

  • Use the Angular CLI schematic — ng generate @angular/core:standalone — to automate migration

Tip
Angular 17+ generates standalone components by default with ng generate component. To opt out and generate NgModule-based components, pass the --no-standalone flag.

Summary: NgModules group declarations, imports, and exports at the module level; standalone components own their imports directly at the component level. Both models coexist, but standalone is the modern default. Understanding both lets you work confidently in any Angular codebase.