Angular

Standalone Components

15 Questions

Standalone components (Angular 14+) do not require an NgModule. They declare their own dependencies directly via the imports array in the @Component decorator. This makes components self-contained and eliminates the need for module declarations. Standalone is the default in Angular 17+.
@Component({
  standalone: true,
  selector: 'app-hello',
  imports: [CommonModule, FormsModule],
  template: '<input [(ngModel)]="name"> Hello {{name}}'
})
export class HelloComponent { name = 'World'; }
Standalone components know exactly what they need. They improve tree-shaking by making dependencies explicit at the component level.

Why it matters: Tests understanding of modern Angular architecture. Shows you know component encapsulation and dependencies.

Real applications: All new Angular projects since 17, microapp components, reusable component libraries, feature modules.

Common mistakes: Developers forget standalone: true. They don't import required modules. They mix NgModules with standalone confusing the architecture.

Use bootstrapApplication from @angular/platform-browser to bootstrap a standalone component directly without any NgModule. Pass the root component and an optional ApplicationConfig object for providers. This replaces the traditional platformBrowserDynamic().bootstrapModule() approach. The config object is where you register global providers.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig);
The appConfig object centralizes all application-level providers like routing, HTTP client, and animations.

Why it matters: Tests understanding of standalone bootstrap mechanism. Shows you know application initialization.

Real applications: New standalone Angular applications, SSR setups, testing environments, hybrid standalone/NgModule apps.

Common mistakes: Developers use old bootstrapModule() with standalone. They don't understand appConfig replaces NgModules. They forget mandatory providers in config.

Use provideRouter in the application config to set up routing for standalone applications. Routes can use loadComponent for lazy loading individual components without needing a module. The routes array defines path-to-component mappings just like in NgModule apps. Additional router features are added using withPreloading, withDebugTracing, etc.
// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient()
  ]
};

// routes
export const routes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'users', loadComponent: () =>
    import('./users/users.component').then(m => m.UsersComponent) }
];
provideRouter replaces RouterModule.forRoot(). Use loadComponent instead of loadChildren for single-component lazy loading.

Why it matters: Tests understanding of standalone routing configuration. Shows you know routing setup without NgModules.

Real applications: Feature routing, nested routes, lazy loading, preloading strategies, route guards.

Common mistakes: Developers use RouterModule.forRoot() in standalone apps (outdated). They use loadChildren for single components. They forget to add routes to config.

Use loadComponent in route definitions for individual component lazy loading. This is simpler than the NgModule approach because you do not need to create a separate module for each lazy-loaded feature. The component and its dependencies are automatically split into a separate bundle. The import() function dynamically loads the component when the route is navigated to.
{ 
  path: 'profile', 
  loadComponent: () => import('./profile/profile.component')
    .then(c => c.ProfileComponent)
}
loadComponent reduces initial bundle size by deferring component code. Each lazy-loaded component gets its own chunk in the build output.

Why it matters: Tests understanding of code splitting for performance. Shows you know lazy loading standalone components.

Real applications: Feature modules, modal dialogs, dashboard widgets, admin interfaces, premium features.

Common mistakes: Developers don't use loadComponent for heavy components keeping them in main bundle. They use loadChildren for single components. They don't understand chunk splitting benefits.

Register global services and providers in the bootstrapApplication config object. Use provide functions like provideHttpClient, provideRouter, and provideAnimations instead of importing modules. This is cleaner because each provider function accepts configuration options directly. Custom services with providedIn: 'root' do not need to be listed here.
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor])),
    provideRouter(routes, withPreloading(PreloadAllModules)),
    provideAnimations(),
    { provide: API_URL, useValue: 'https://api.example.com' }
  ]
});
The provide functions are tree-shakable alternatives to NgModule imports. They allow fine-grained configuration of each feature.

Why it matters: Tests understanding of provider registration in standalone apps. Shows you know dependency injection setup.

Real applications: HTTP interceptors, routing configuration, animation providers, third-party library setup, global services.

Common mistakes: Developers mix provide functions and NgModules. They don't import necessary providers. They register services wrong without providedIn: 'root'.

Yes, directives and pipes can also be standalone by setting standalone: true in their decorator. This allows them to be imported directly by other standalone components without needing an NgModule. They work exactly like standalone components but for reusable behaviors and data transformations. Import them in any component's imports array.
@Directive({ standalone: true, selector: '[appHighlight]' })
export class HighlightDirective { }

@Pipe({ standalone: true, name: 'truncate' })
export class TruncatePipe implements PipeTransform { }
Standalone directives and pipes are self-contained and portable. They can be shared across multiple components without a shared module.

Why it matters: Tests understanding of extending standalone architecture to directives/pipes. Shows you know full standalone patterns.

Real applications: Custom directives, data formatting pipes, reusable behaviors across components, shared formatting logic.

Common mistakes: Developers don't mark directives/pipes as standalone. They still use SharedModule for common directives. They don't import standalone directives individually.

Angular provides a migration schematic that automates the conversion from NgModules to standalone components. Run the schematic to mark components as standalone, add their imports arrays, and remove NgModule declarations. The migration happens in stages so you can migrate incrementally. Eventually you can remove NgModules entirely.
ng generate @angular/core:standalone
Steps: mark components/directives/pipes as standalone, add imports array to each, remove from NgModule declarations, then remove the NgModule. Migrate incrementally to avoid breaking changes.

Why it matters: Tests understanding of migration strategies. Shows you can upgrade large NgModule codebases.

Real applications: Large enterprise applications, gradual rewrites, mixed architecture during transition, legacy system modernization.

Common mistakes: Developers migrate everything at once breaking things. They don't use the schematic forgetting it exists. They don't understand incremental migration benefits.

importProvidersFrom extracts providers from NgModules for use in standalone applications. This is the bridge between NgModule-based libraries and standalone apps. When a library only exports an NgModule with providers (like forRoot()), use importProvidersFrom to extract those providers. It returns the providers without the NgModule wrapper.
bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(SomeLibraryModule.forRoot())
  ]
});
Use importProvidersFrom only for legacy NgModule libraries. Modern libraries provide standalone-compatible provide functions instead.

Why it matters: Tests understanding of integrating NgModule libraries into standalone apps. Shows you know bridge patterns.

Real applications: Using older libraries in new standalone apps, gradual library upgrades, open-source library compatibility.

Common mistakes: Developers use importProvidersFrom for new libraries. They don't understand it's a migration tool. They apply it to well-maintained libraries that support standalone.

Import standalone components directly in an NgModule's imports array, not in declarations. This allows you to use standalone components in existing NgModule-based applications. The standalone component brings all its own dependencies so the NgModule does not need to import them. This enables gradual migration to standalone.
@NgModule({
  imports: [StandaloneHelloComponent], // Standalone in imports, not declarations
  declarations: [AppComponent]
})
Standalone components go in imports, not declarations. They are treated like modules that provide themselves.

Why it matters: Tests understanding of mixed architecture patterns. Shows you can use standalone in NgModule apps.

Real applications: Gradual migration, importing standalone libraries, mixed NgModule/standalone architectures, feature integration.

Common mistakes: Developers put standalone components in declarations. They don't understand standalone components replace module functionality. They forget standalone components import dependencies.

Standalone components offer several key benefits over NgModule-based architecture. They provide a simplified mental model where no NgModules are needed. Dependencies are explicit, enabling better tree-shaking since you only import what you use. Lazy loading is easier with loadComponent.
// Benefits demonstrated
@Component({
  standalone: true,
  imports: [CommonModule, RouterModule], // Explicit dependencies
  template: '...'
})
export class FeatureComponent { }

// Easy lazy loading
{ path: 'feature', loadComponent: () =>
  import('./feature.component').then(c => c.FeatureComponent) }
Standalone components are self-contained and portable. They reduce boilerplate and are the default architecture in Angular 17+.

Why it matters: Tests understanding of standalone advantages. Shows you know why projects are shifting to this architecture.

Real applications: Better tree-shaking reducing bundle size, simpler mental model, easier testing, cleaner dependency management.

Common mistakes: Developers don't understand tree-shaking benefits. They think standalone is only for new projects. They don't know how much boilerplate it eliminates.

Standalone components declare their own dependencies directly in the imports array of their @Component decorator. NgModule-based components rely on the NgModule they belong to for dependency management. Standalone components are self-contained and know exactly what they need. NgModule components inherit available declarations and imports from their module.
// Standalone: self-contained dependencies
@Component({
  standalone: true,
  imports: [CommonModule, FormsModule, DatePipe],
  template: '...'
})
export class StandaloneComponent { }

// NgModule-based: depends on the module
@Component({ template: '...' })
export class ModuleComponent { }
// Needs to be declared in a module that imports CommonModule, etc.
Standalone is the modern approach that simplifies architecture and improves tree-shaking by making dependencies explicit at the component level.

Why it matters: Tests understanding of architectural evolution in Angular. Shows you know paradigm differences.

Real applications: New projects, component libraries, microapps, feature modules, performance-critical apps.

Common mistakes: Developers continue using NgModules in new projects. They don't understand implicit vs explicit dependency management. They underestimate tree-shaking improvements.

With standalone components, import individual Angular Material modules directly in the component's imports array. This is better for tree-shaking because you only import the specific Material modules you need. Each component declares exactly which Material buttons, inputs, or other components it uses, making the dependency tree explicit.
@Component({
  standalone: true,
  imports: [
    MatButtonModule,
    MatInputModule,
    MatCardModule,
    MatIconModule
  ],
  template: '<mat-card>' +
    '<mat-form-field><input matInput /></mat-form-field>' +
    '<button mat-raised-button>Submit</button>' +
    '</mat-card>'
})
export class FormComponent { }
This eliminates the need for a large SharedModule that imports all Material components. Only the components you use end up in the bundle.

Why it matters: Tests understanding of component library integration. Shows you know Material with modern Angular.

Real applications: Angular Material dashboards, design system components, material design interfaces, UI libraries.

Common mistakes: Developers create a SharedModule importing all Material losing tree-shaking. They don't understand per-component imports. They don't leverage standalone Material benefits.

When many standalone components need the same set of imports, create a shared array of commonly used modules and spread them into each component's imports. This avoids repeating long import lists. Create a utility file that exports the common set. This is the standalone equivalent of a SharedModule but without the NgModule overhead.
// shared-imports.ts
export const COMMON_IMPORTS = [
  CommonModule,
  FormsModule,
  RouterModule,
  DatePipe,
  UpperCasePipe
] as const;

// Component uses it
@Component({
  standalone: true,
  imports: [...COMMON_IMPORTS, MatButtonModule],
  template: '...'
})
export class MyComponent { }
Use as const for type safety. Spread the array with ... to include all common imports plus component-specific ones.

Why it matters: Tests understanding of code reuse patterns in standalone. Shows you can manage complexity at scale.

Real applications: Large applications with many components, design systems, component libraries, consistent styling patterns.

Common mistakes: Developers repeat imports in every component. They create a barrel file instead of using arrays. They don't use as const losing type safety.

Use loadComponent for single components and loadChildren with a function returning a routes array for route groups. This is simpler than NgModule lazy loading because you do not need a separate module. Each route or group of routes can be loaded on demand, reducing the initial bundle size.
export const routes: Routes = [
  // Lazy load a single component
  { path: 'profile', loadComponent: () =>
    import('./profile/profile.component').then(c => c.ProfileComponent)
  },
  // Lazy load a group of child routes
  { path: 'admin', loadChildren: () =>
    import('./admin/admin.routes').then(r => r.ADMIN_ROUTES)
  }
];

// admin/admin.routes.ts
export const ADMIN_ROUTES: Routes = [
  { path: '', component: AdminDashComponent },
  { path: 'users', component: AdminUsersComponent }
];
loadChildren with a routes array replaces the NgModule lazy loading pattern. The routes file is a simple TypeScript file exporting a Routes array.

Why it matters: Tests understanding of advanced lazy loading with standalone components. Shows you can organize large applications.

Real applications: Admin panels (lazy), customer dashboards (lazy), feature-specific routes, large feature modules, performance-optimized routing.

Common mistakes: Developers don't lazy load modules losing performance. They mix loadComponent and loadChildren patterns. They put routes in components instead of route files.

Starting from Angular 17, the CLI generates standalone components by default. When you run ng new, the application is bootstrapped without any NgModule using bootstrapApplication(). When you run ng generate component, it creates a standalone component with standalone: true. You can still create NgModule-based apps with the --standalone=false flag.

Why it matters: Tests understanding of Angular's current defaults. Shows you know modern best practices.

Real applications: New projects (default standalone), legacy projects (NgModules), enterprise transitions, teams learning Angular.

Common mistakes: Developers don't know standalone is default missing out on advantages. They still teach/learn NgModules first. They assume their existing knowledge applies to new projects.

// Angular 17+ default: no NgModule
// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient()
  ]
});

// Generated component is standalone by default
@Component({
  standalone: true,
  imports: [CommonModule],
  template: '<p>works!</p>'
})
export class NewComponent { }
This reflects Angular's direction of making standalone the primary architecture pattern going forward. NgModules are still supported but no longer the default.