ng build --configuration production // AOT is default in production
AOT catches template errors like typos in property names before deployment. JIT (Just-in-Time) compiles at runtime and is used only during development.
Why it matters: Tests understanding of Angular build optimization. Shows you know compilation strategies.
Real applications: Production builds use AOT by default for faster rendering and smaller bundles.
Common mistakes: Developers don't understand AOT is default in production. They use JIT in production by mistake. They don't know template errors are caught at build time with AOT.
// Tree-shakable service: removed if never injected
@Injectable({ providedIn: 'root' })
export class UnusedService { } // removed from bundle
// Not tree-shakable: always included
providers: [AlwaysIncludedService]
Use providedIn: 'root' instead of adding services to providers arrays. Import only what you need from libraries for better tree shaking.
Why it matters: Tree shaking reduces bundle size significantly. Shows you know build optimization techniques.
Real applications: Large applications with many unused services benefit from tree shaking removing hundreds of KB of code.
Common mistakes: Developers don't use providedIn: 'root' losing tree shaking benefits. They import entire libraries when only one function is needed. They don't understand static analysis limitations.
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
trackById(index: number, item: Item): number {
return item.id; // Angular reuses DOM for items with same id
}
Always use trackBy for lists that change frequently or contain many items. The track function should return a unique identifier like an id or key.
Why it matters: trackBy prevents DOM thrashing in large lists. Shows you know list rendering optimization.
Real applications: Tables with thousands of rows, infinite scrolling lists, real-time updating dashboards.
Common mistakes: Developers forget trackBy entirely. They use array index as identifier (wrong for sorted/filtered lists). They use complex tracking functions that trigger unnecessary re-renders.
{ path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
// or standalone
{ path: 'admin', loadComponent: () => import('./admin.component').then(c => c.AdminComponent) }
Lazy loading is one of the most impactful performance optimizations. Combine with preloading strategies for instant navigation after initial load.
Why it matters: Lazy loading dramatically reduces initial bundle size. Shows you know code splitting strategies.
Real applications: Large applications with admin interfaces, premium features, or extensive feature sets all benefit from lazy loading.
Common mistakes: Developers don't use lazy loading so large bundles slow initial load. They over-eagerly load everything. They don't combine with preloading for performance.
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items">{{ item.name }}</div>
</cdk-virtual-scroll-viewport>
Import ScrollingModule from @angular/cdk/scrolling. Virtual scrolling can handle lists with hundreds of thousands of items smoothly.
Why it matters: Virtual scrolling enables rendering massive lists efficiently. Shows you know advanced scrolling optimization.
Real applications: Grids with millions of rows, real-time data feeds, large list interfaces.
Common mistakes: Developers render all DOM elements for large lists causing browser freeze. They don't use virtual scrolling when needed. They set incorrect itemSize causing layout issues.
ng build --stats-json
npx webpack-bundle-analyzer dist/my-app/stats.json
The analyzer shows a visual treemap of your bundle. Look for unexpectedly large dependencies and consider dynamic imports or lighter alternatives.
Why it matters: Bundle analysis identifies bloat and optimization opportunities. Shows you can profile and optimize builds.
Real applications: Large applications use bundle analysis to find unexpected large dependencies or unused imports.
Common mistakes: Developers don't analyze bundles assuming they're optimized. They import everything from large libraries when only a function is needed. They don't remove unused dependencies.
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
// or QuicklinkStrategy for only visible links
Preloading happens during idle time so it does not affect initial load performance. Use custom strategies to prioritize which modules to preload first.
Why it matters: Preloading balances bundle size with navigation speed. Shows you know advanced routing optimization.
Real applications: Best of both worlds: small initial bundle plus instant navigation after load completes.
Common mistakes: Developers use PreloadAllModules on small apps wasting resources. They don't create custom strategies for larger apps. They don't understand preloading is only for idle time.
// Template: auto-subscribes and unsubscribes
<div *ngIf="data$ | async as data">
<p>{{ data.name }}</p>
</div>
// Component: no subscribe/unsubscribe needed
data$ = this.http.get<User>('/api/user');
The async pipe is the recommended way to consume Observables in templates. It reduces boilerplate and prevents common memory leak bugs.@defer (on viewport) {
<app-heavy-chart [data]="data" />
} @placeholder {
<div>Loading chart...</div>
} @loading (minimum 500ms) {
<app-spinner />
}
@defer triggers include on viewport, on interaction, on hover, on idle, and on timer. It reduces initial bundle size by deferring heavy components.
Why it matters: @defer enables declarative code splitting. Shows you know template-level lazy loading strategies.
Real applications: Heavy charts, data grids, modals, and below-the-fold content benefit from deferred rendering.
Common mistakes: Developers don't use @defer leaving heavy components in initial bundle. They defer components that should load eagerly. They don't use placeholder/loading states for good UX.
<img ngSrc="hero.jpg" width="400" height="300" priority />
NgOptimizedImage can integrate with image CDNs for automatic format conversion and resizing. Using it correctly improves Core Web Vitals scores.
Why it matters: NgOptimizedImage enforces best practices automatically. Shows you know image performance optimization.
Real applications: E-commerce sites, galleries, content platforms all depend on optimized image loading for fast performance.
Common mistakes: Developers use regular img tags without lazy loading causing performance issues. They don't set width/height causing layout shift. They don't mark priority images for preloading.
@defer (on viewport) {
<app-heavy-chart [data]="chartData" />
} @placeholder {
<div>Chart will load here</div>
} @loading (minimum 500ms) {
<app-spinner />
} @error {
<p>Failed to load chart</p>
}
The @placeholder shows before loading starts. The @loading block appears during the download. minimum prevents flicker by ensuring the loading state shows for a minimum duration.
Why it matters: @defer gives you template-level control over code splitting. Shows you know modern performance patterns.
Real applications: Modal dialogs, tabs, nested features, and conditional UI benefit from declarative lazy loading.
Common mistakes: Developers use component lazy routes when @defer is simpler. They don't understand @defer creates separate chunks. They don't combine @defer with other optimizations.
// Pure pipe: only runs when 'value' reference changes
@Pipe({ name: 'expensive', pure: true })
export class ExpensivePipe implements PipeTransform {
transform(value: Data[]): Data[] {
return value.filter(item => item.active).sort((a, b) => a.name.localeCompare(b.name));
}
}
Using pure pipes with immutable data patterns gives you free memoization. Always prefer pure pipes and create new array/object references when data changes.
Why it matters: Pure pipes enable automatic memoization. Shows you know caching and optimization patterns.
Real applications: Complex data transformations like sorting, filtering, formatting benefit from pure pipe caching.
Common mistakes: Developers use impure pipes (pure: false) adding performance overhead. They mutate arrays/objects breaking memoization. They create new transforms inside impure pipes repeatedly.
// Angular 17+ SSR setup
ng add @angular/ssr
// app.config.server.ts
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering()
]
};
After the initial render, Angular hydrates the page on the client side, attaching event listeners and making it interactive. SSR is essential for content-heavy and SEO-dependent applications.
Why it matters: SSR improves performance and SEO significantly. Shows you know full-stack optimization techniques.
Real applications: Blogs, news sites, e-commerce platforms, and content-driven applications depend on SSR for performance and SEO.
Common mistakes: Developers don't use SSR for content apps losing SEO benefits. They use SSR everywhere including admin dashboards (unnecessary). They don't handle browser-only APIs (localStorage) in SSR code.
import { NgOptimizedImage } from '@angular/common';
@Component({
standalone: true,
imports: [NgOptimizedImage],
template: '<img ngSrc="hero.jpg" width="800" height="400" priority />' +
'<img ngSrc="photo.jpg" width="400" height="300" />'
})
export class GalleryComponent { }
It can integrate with image CDNs for automatic optimization and format conversion. Using it correctly improves Core Web Vitals scores significantly.
Why it matters: NgOptimizedImage automates best practices enforcement. Shows you know image loading optimization.
Real applications: Photo galleries, e-commerce product images, content platforms, and hero sections benefit from NgOptimizedImage.
Common mistakes: Developers don't use ngSrc forgetting lazy loading benefits. They forget width/height attributes causing cumulative layout shift. They don't mark LCP images with priority attribute.
// Lazy loading: loaded only on navigation
{ path: 'admin', loadComponent: () => import('./admin.component') }
// With preloading: loaded in background after app starts
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})
Use PreloadAllModules for small apps or a custom strategy for larger apps to prioritize which modules to preload first.
Why it matters: Understanding both lazy loading and preloading is essential for bundle optimization. Shows you know available tradeoffs.
Real applications: Large applications balance these strategies: lazy load features with low priority and preload likely navigation paths.
Common mistakes: Developers use PreloadAllModules for all apps losing bundle size benefits. They don't create custom strategies for real optimization. They don't monitor network patterns before choosing preloading strategies.