Angular

Modules

15 Questions

An NgModule is a class decorated with @NgModule() that organizes related code into cohesive blocks. It contains metadata about declarations (components, directives, pipes), imports (other modules), exports, and providers (services). The root module is AppModule which bootstraps the application. Feature modules group related functionality together.
@NgModule({
  declarations: [AppComponent, HeaderComponent],
  imports: [BrowserModule, FormsModule],
  providers: [DataService],
  bootstrap: [AppComponent]
})
export class AppModule { }
Every Angular application has at least one root NgModule. With Angular 14+ standalone components, NgModules are optional but still widely used.

Why it matters: Tests understanding of Angular module basics. Shows you know how applications are organized.

Real applications: Every Angular project relies on NgModules or standalone setup. Understanding modules is essential for structuring large applications.

Common mistakes: Developers don't understand why modules exist. They declare components in multiple modules. They confuse NgModule with ES modules.

Declarations register components, directives, and pipes that belong to this module. Imports bring in other modules whose exported classes are needed in this module's templates. Exports make declarations and imported modules available to other modules that import this one. Providers register services available to the module's injector.
@NgModule({
  declarations: [MyComponent, MyDirective, MyPipe],
  imports: [CommonModule, FormsModule],
  exports: [MyComponent, MyPipe],
  providers: [DataService]
})
export class FeatureModule { }
A component can only appear in one module's declarations. To share it across modules, export it from a shared module and import that module elsewhere.

Why it matters: Tests understanding of module architecture. Shows you know declaration, import, and export roles.

Real applications: Shared UI libraries (buttons, modals), common directives across features, reusable pipes for formatting.

Common mistakes: Developers declare components in multiple modules causing errors. They don't export components when sharing them. They don't understand import/export relationships.

A feature module encapsulates a set of related functionality like a product catalog or admin panel. It helps organize code into logical blocks and enables lazy loading. Feature modules use RouterModule.forChild() for their own routes. They import CommonModule instead of BrowserModule.
@NgModule({
  declarations: [ProductListComponent, ProductDetailComponent],
  imports: [CommonModule, RouterModule.forChild(routes)],
  exports: [ProductListComponent]
})
export class ProductsModule { }
Feature modules keep the AppModule lean and make the codebase easier to maintain. They can be eagerly or lazily loaded.

Why it matters: Tests understanding of code organization patterns. Shows you know feature-based architecture.

Real applications: Admin feature, e-commerce products catalog, user profile management, settings module.

Common mistakes: Developers put everything in AppModule. They don't understand lazy loading benefits. They forget to use CommonModule in feature modules.

A shared module contains common components, directives, and pipes used across multiple feature modules. It avoids duplicating declarations and keeps code DRY. You export everything that other modules need from this module. It is common to re-export CommonModule so importing modules do not need to import it separately.
@NgModule({
  declarations: [SpinnerComponent, HighlightDirective, TruncatePipe],
  imports: [CommonModule],
  exports: [SpinnerComponent, HighlightDirective, TruncatePipe, CommonModule]
})
export class SharedModule { }
Do not put services in a shared module's providers, use providedIn: 'root' instead. Shared module providers create separate instances in lazy-loaded modules.

Why it matters: Tests understanding of service scoping and dependency injection. Shows you know singleton pattern concerns.

Real applications: AuthService available globally, DataService used across features, ConfigService for app-wide settings.

Common mistakes: Developers put services in shared module providers. They expect services to be singletons but get multiple instances. They don't use providedIn: 'root'.

Lazy loading loads feature modules on demand when the user navigates to their routes. This reduces the initial bundle size and improves application startup time. Angular creates a separate JavaScript chunk for each lazy-loaded module during the build. Use the loadChildren property with a dynamic import in the route configuration.
const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('./products/products.module')
      .then(m => m.ProductsModule)
  }
];
Lazy-loaded modules get their own child injector, so services provided in them are scoped to that module. Use preloading strategies to load modules in the background after the initial load.

Why it matters: Tests understanding of lazy loading benefits and performance optimization. Shows you know module scoping implications.

Real applications: Admin dashboard lazy-loaded only for admin users, reporting feature loaded on-demand, maps module loaded when user navigates.

Common mistakes: Developers eager-load everything, defeating performance gains. They don't understand child injector scoping. They don't use preloading strategies.

forRoot() registers providers for the entire app (use in AppModule). forChild() registers routes for feature modules without re-registering providers.

// AppModule
RouterModule.forRoot(appRoutes)

// Feature module
RouterModule.forChild(featureRoutes)

Why it matters: Tests understanding of module setup patterns. Shows you know provider registration strategies.

Real applications: Root module setup with forRoot for routing and authentication, feature modules with forChild for isolated routes.

Common mistakes: Developers use forRoot in multiple modules causing duplicate providers. They don't use forChild for feature modules. They don't understand the provider registration difference.

CoreModule contains singleton services and one-time setup. It should only be imported in AppModule.

@NgModule({
  providers: [AuthService, LoggerService]
})
export class CoreModule {
  constructor(@Optional() @SkipSelf() parent: CoreModule) {
    if (parent) throw new Error('CoreModule already loaded!');
  }
}

Why it matters: Tests understanding of singleton patterns and guard mechanisms. Shows you know how to prevent accidental module duplication.

Real applications: AppModule must only import CoreModule once, preventing duplicate services for global features.

Common mistakes: Developers don't use the constructor guard and CoreModule gets imported twice. They don't use @SkipSelf and @Optional together. They don't throw an error when duplication is detected.

No, a component can be declared in only one NgModule. Declaring it in multiple modules causes a compile-time error. If you need the component in multiple modules, declare it in a shared module and export it. Then import that shared module wherever you need the component.
// SharedModule declares and exports the component
@NgModule({
  declarations: [SharedButtonComponent],
  exports: [SharedButtonComponent]
})
export class SharedModule { }

// Feature modules import SharedModule
@NgModule({ imports: [SharedModule] })
export class FeatureAModule { }
With standalone components in Angular 14+, this restriction no longer applies because standalone components manage their own dependencies.

Why it matters: Tests understanding of module declaration rules. Shows you know the benefits of standalone components.

Real applications: Shared button component available to any feature, modal dialogs, reusable form inputs.

Common mistakes: Developers declare components in multiple modules before using standalone. They don't understand why single declaration matters. They forget to export from shared modules.

BrowserModule includes CommonModule plus browser-specific providers like DOM rendering and sanitization. Use BrowserModule only in the root AppModule because it initializes the browser platform. Use CommonModule in all feature modules for directives like ngIf and ngFor. Importing BrowserModule in a feature module causes an error.
// Root module: BrowserModule (only once)
@NgModule({ imports: [BrowserModule] })
export class AppModule { }

// Feature modules: CommonModule
@NgModule({ imports: [CommonModule] })
export class FeatureModule { }
CommonModule provides core directives and pipes without the browser platform setup. Always use it instead of BrowserModule in feature modules.

Why it matters: Tests understanding of module setup differences. Shows you know when to use each module.

Real applications: Root AppModule imports BrowserModule once, every feature module imports CommonModule for ngIf and ngFor.

Common mistakes: Developers import BrowserModule in feature modules causing errors. They don't import CommonModule thinking directives work automatically. They use BrowserModule multiple times.

Preloading strategies load lazy modules in the background after the app starts, improving subsequent navigation speed. Angular provides PreloadAllModules which loads all lazy modules. You can also create custom strategies that selectively preload based on route data flags. This balances initial load performance with navigation responsiveness.
RouterModule.forRoot(routes, {
  preloadingStrategy: PreloadAllModules
})

// Or use custom strategy
export class CustomPreloader implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>) {
    return route.data?.['preload'] ? load() : of(null);
  }
}
PreloadAllModules is good for small apps. Use a custom strategy for larger apps to preload only frequently accessed routes.

Why it matters: Tests understanding of performance optimization through preloading. Shows you know balancing initial load vs navigation speed.

Real applications: E-commerce preloads products and checkout modules, admin dashboards preload frequently accessed reports, SPA preloads main feature modules.

Common mistakes: Developers don't understand preloading benefits. They preload all modules harming initial load. They don't use custom strategies for selective preloading.

When a module is imported into multiple other modules, each importing module gets access to the exported declarations of the shared module. Services provided in the shared module get a separate instance in each lazy-loaded module, but a shared instance in eagerly loaded modules. To avoid multiple instances of services, use providedIn: 'root' instead of putting services in shared module providers. Component declarations remain shared across all importing modules properly.

@NgModule({
  declarations: [SpinnerComponent],
  exports: [SpinnerComponent] // available to all importers
  // Do NOT add services here — use providedIn: 'root' instead
})
export class SharedModule { }
\n\n

\n\n

Why it matters: Tests understanding of module importing patterns and service scoping. Shows you know injection and declaration scope differences.

\n\n

Real applications: Shared button component used by admin and user modules, directive that formats dates across features, service providing user data to multiple features.

\n\n

Common mistakes: Developers think shared services are global (they're not in lazy modules). They import same module multiple times by mistake. They don't understand scoping differences between declarations and providers.

\n

Declarations is where you register components, directives, and pipes that belong to this module. Imports is where you bring in other NgModules whose exported classes you need in your templates. For example, if your template uses ngIf or ngFor, you need to import CommonModule. A component can only be declared in one module, but modules can be imported into many modules.
@NgModule({
  declarations: [MyComponent, MyDirective, MyPipe], // own items
  imports: [CommonModule, FormsModule, SharedModule], // other modules
  exports: [MyComponent] // make available to other modules
})
Only @Component, @Directive, and @Pipe classes go in declarations. Modules and services should never be placed in the declarations array.

Why it matters: Tests understanding of NgModule metadata arrays. Shows you know what each array is for.

Real applications: Every module declaration section filters what goes in declarations vs imports vs exports.

Common mistakes: Developers put NgModules in declarations. They put services in declarations. They don't understand the purpose of each metadata array.

Add a constructor guard that checks if CoreModule has already been loaded. Inject the module itself using @Optional() and @SkipSelf() decorators. If the parent injector already has CoreModule, it means it was imported before, so throw an error. This pattern ensures singleton services are not accidentally duplicated.
@NgModule({
  providers: [AuthService, LoggerService]
})
export class CoreModule {
  constructor(@Optional() @SkipSelf() parent: CoreModule) {
    if (parent) {
      throw new Error('CoreModule is already loaded. Import it only in AppModule.');
    }
  }
}
@SkipSelf() looks for CoreModule in parent injectors only. @Optional() prevents an error if it is not found, meaning this is the first import.

Why it matters: Tests understanding of dependency injection and guard patterns. Shows you know singleton service protection.

Real applications: AuthService initialized once in CoreModule, LoggerService configured globally, HttpInterceptor setup controlled.

Common mistakes: Developers forget the constructor guard and CoreModule gets imported twice. They don't use @SkipSelf and @Optional together. They don't throw an error when duplication is detected.

The exports array makes declarations and imported modules available to other modules that import this one. If you declare a component but do not export it, it can only be used within this module's own templates. You can also re-export modules like CommonModule so importing modules get ngIf, ngFor, etc. automatically. Exporting makes sense for shared and reusable UI components.
@NgModule({
  declarations: [ButtonComponent, TooltipDirective],
  imports: [CommonModule],
  exports: [
    ButtonComponent,     // export own declarations
    TooltipDirective,
    CommonModule         // re-export so importers get ngIf, ngFor etc.
  ]
})
export class SharedModule { }
Only exported declarations are visible to importing modules. Non-exported declarations are private to the declaring module.

Why it matters: Tests understanding of module encapsulation. Shows you know export semantics and visibility scoping.

Real applications: Shared component libraries export only public UI components, internal components stay private, re-exporting CommonModule for convenience.

Common mistakes: Developers declare components but forget to export. They assume all declarations are visible (they're not without export). They export internal components unnecessarily.

No, NgModules are optional starting from Angular 14+. Standalone components, directives, and pipes manage their own dependencies through their imports array. New projects generated with Angular 17+ use standalone by default with no NgModules. However, NgModules are still fully supported for existing applications and third-party libraries.
// Standalone: no NgModule needed
@Component({
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: '...'
})
export class MyComponent { }

// Bootstrap without NgModule
bootstrapApplication(AppComponent, { providers: [...] });
You can mix standalone and NgModule-based components in the same application during gradual migration. The Angular CLI provides schematics to help with the migration process.

Why it matters: Tests understanding of modern Angular evolution. Shows you know the standalone components alternative.

Real applications: Migrating legacy applications component-by-component, new features use standalone while old uses modules, third-party libraries still use NgModules.

Common mistakes: Developers think NgModules are completely replaced (they're not yet). They don't know standalone components manage their own dependencies. They don't understand gradual migration is possible.