@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.
// 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.
// 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.
{
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.
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'.
@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.
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.
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.
@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.
// 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: 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.
@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.
// 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.
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.
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.