Angular Performance Metrics
In this comprehensive guide, we'll explore crucial performance metrics for Angular applications, understand how to measure them, and learn techniques to improve your app's speed and responsiveness.
Introduction to Performance Metrics
Performance metrics are quantifiable measurements that help us understand how well our Angular applications are performing. Monitoring these metrics allows developers to identify bottlenecks, set performance budgets, and make data-driven optimization decisions.
For Angular applications, performance is not just about how fast your app loads initially, but also how responsive it feels as users interact with it.
Key Angular Performance Metrics
Let's explore the essential metrics you should monitor in your Angular applications:
1. Load Time Metrics
First Contentful Paint (FCP)
FCP measures the time from when the page starts loading to when any part of the page's content is rendered on the screen.
Time to Interactive (TTI)
TTI measures how long it takes for a page to become fully interactive and responsive to user input.
First Input Delay (FID)
FID measures the time from when a user first interacts with your site (clicks a button, taps on a link, etc.) to when the browser is actually able to respond to that interaction.
Largest Contentful Paint (LCP)
LCP reports the render time of the largest content element visible in the viewport.
2. Runtime Performance Metrics
Change Detection Time
How long it takes Angular to detect and respond to state changes in your application.
Memory Usage
The amount of memory your application consumes over time.
Frame Rate
Measured in frames per second (fps), this indicates how smoothly your application renders, especially during animations and scrolling.
Measuring Angular Performance Metrics
Using Chrome DevTools
Chrome DevTools provides powerful features for performance monitoring:
// First, enable performance monitoring in your application
import { enableDebugTools } from '@angular/platform-browser';
import { ApplicationRef } from '@angular/core';
@NgModule({
// ...other module config
})
export class AppModule {
constructor(applicationRef: ApplicationRef) {
// Enable debugging tools
enableDebugTools(applicationRef.components[0]);
}
}
Using Lighthouse
Lighthouse is an open-source automated tool for improving the quality of web pages:
- Open Chrome DevTools (F12)
- Navigate to the "Lighthouse" tab
- Select "Performance" and click "Generate report"
Angular Performance Profiler
Angular provides a built-in profiler to measure change detection performance:
// In your component or service
import { NgZone } from '@angular/core';
constructor(private ngZone: NgZone) {
// Run outside Angular's change detection to measure performance
this.ngZone.runOutsideAngular(() => {
// Measure performance
const startTime = performance.now();
// Your code to measure
const endTime = performance.now();
console.log(`Operation took ${endTime - startTime} ms`);
});
}
Real-World Application: Measuring and Improving Load Time
Let's create a simple performance monitoring service to track metrics in an Angular application:
// performance.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class PerformanceService {
private metrics: Record<string, number> = {};
startMeasurement(metricName: string): void {
if (!window.performance) {
console.warn('Performance API not supported');
return;
}
const startMark = `${metricName}_start`;
performance.mark(startMark);
}
stopMeasurement(metricName: string): number {
if (!window.performance) {
return 0;
}
const startMark = `${metricName}_start`;
const endMark = `${metricName}_end`;
performance.mark(endMark);
try {
performance.measure(metricName, startMark, endMark);
const entries = performance.getEntriesByName(metricName);
const duration = entries[0].duration;
this.metrics[metricName] = duration;
console.log(`Metric ${metricName}: ${duration.toFixed(2)}ms`);
// Clean up marks and measures
performance.clearMarks(startMark);
performance.clearMarks(endMark);
performance.clearMeasures(metricName);
return duration;
} catch (e) {
console.error('Error measuring performance:', e);
return 0;
}
}
getMetrics(): Record<string, number> {
return { ...this.metrics };
}
}
Using this service in a component:
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { PerformanceService } from './performance.service';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
template: `
<div>
<h1>Performance Metrics Demo</h1>
<button (click)="loadData()">Load Data</button>
<div *ngIf="isLoading">Loading...</div>
<ul>
<li *ngFor="let item of items">{{ item.title }}</li>
</ul>
<div *ngIf="metrics.dataLoadTime">
<h3>Performance Metrics:</h3>
<p>Data Load Time: {{ metrics.dataLoadTime.toFixed(2) }}ms</p>
<p>Rendering Time: {{ metrics.renderTime.toFixed(2) }}ms</p>
</div>
</div>
`
})
export class AppComponent implements OnInit {
items: any[] = [];
isLoading = false;
metrics: Record<string, number> = {};
constructor(
private http: HttpClient,
private performanceService: PerformanceService
) {}
ngOnInit() {
this.loadData();
}
loadData() {
this.isLoading = true;
this.items = [];
// Start measuring data loading time
this.performanceService.startMeasurement('dataLoadTime');
this.http.get<any[]>('https://jsonplaceholder.typicode.com/posts')
.subscribe(data => {
// Stop measuring data loading time
const dataLoadTime = this.performanceService.stopMeasurement('dataLoadTime');
// Start measuring rendering time
this.performanceService.startMeasurement('renderTime');
this.items = data;
this.isLoading = false;
// We need to wait for change detection to complete
setTimeout(() => {
const renderTime = this.performanceService.stopMeasurement('renderTime');
this.metrics = this.performanceService.getMetrics();
});
});
}
}
Setting Performance Budgets
Performance budgets are predefined thresholds for metrics that you don't want to exceed. Angular allows you to set these in your angular.json
file:
{
"projects": {
"app": {
"architect": {
"build": {
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
},
{
"type": "bundle",
"name": "main",
"maximumWarning": "300kb",
"maximumError": "500kb"
}
]
}
}
}
}
}
}
}
Core Web Vitals and Angular
Core Web Vitals are a set of specific factors that Google considers important for a webpage's overall user experience:
- Largest Contentful Paint (LCP): Measures loading performance
- First Input Delay (FID): Measures interactivity
- Cumulative Layout Shift (CLS): Measures visual stability
To monitor Core Web Vitals in your Angular application:
// web-vitals.service.ts
import { Injectable } from '@angular/core';
import { ReportHandler } from 'web-vitals';
@Injectable({
providedIn: 'root'
})
export class WebVitalsService {
measureWebVitals() {
if ('performance' in window &&
'timing' in window.performance &&
'navigation' in window.performance) {
try {
import('web-vitals').then(({ getCLS, getFID, getLCP }) => {
getCLS(this.reportWebVital);
getFID(this.reportWebVital);
getLCP(this.reportWebVital);
});
} catch (e) {
console.error('Error loading web-vitals', e);
}
}
}
private reportWebVital: ReportHandler = (metric) => {
console.log(metric.name, metric.value);
// You can send this data to your analytics service
// this.analyticsService.sendMetric(metric.name, metric.value);
}
}
To use this service, inject it into your root component and call measureWebVitals()
in the ngOnInit
method.
Practical Example: Performance Dashboard Component
Let's create a reusable performance dashboard component to visualize our metrics:
// performance-dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { PerformanceService } from './performance.service';
import { WebVitalsService } from './web-vitals.service';
@Component({
selector: 'app-performance-dashboard',
template: `
<div class="performance-dashboard">
<h2>Performance Dashboard</h2>
<div class="metric-section">
<h3>Load Time Metrics</h3>
<div class="metric-card" *ngIf="fcpTime !== null">
<div class="metric-label">First Contentful Paint</div>
<div class="metric-value" [ngClass]="getFCPClass()">
{{ fcpTime.toFixed(0) }} ms
</div>
</div>
<div class="metric-card" *ngIf="lcpTime !== null">
<div class="metric-label">Largest Contentful Paint</div>
<div class="metric-value" [ngClass]="getLCPClass()">
{{ lcpTime.toFixed(0) }} ms
</div>
</div>
</div>
<div class="metric-section">
<h3>Custom Application Metrics</h3>
<div class="metric-card" *ngFor="let metric of customMetrics">
<div class="metric-label">{{ formatMetricName(metric.name) }}</div>
<div class="metric-value">{{ metric.value.toFixed(2) }} ms</div>
</div>
</div>
</div>
`,
styles: [`
.performance-dashboard {
background-color: #f5f5f5;
border-radius: 8px;
padding: 16px;
margin: 16px 0;
}
.metric-section {
margin-bottom: 16px;
}
.metric-card {
background-color: white;
border-radius: 4px;
padding: 12px;
margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
}
.metric-label {
font-weight: 500;
}
.metric-value {
font-family: monospace;
}
.good {
color: green;
}
.needs-improvement {
color: orange;
}
.poor {
color: red;
}
`]
})
export class PerformanceDashboardComponent implements OnInit {
fcpTime: number | null = null;
lcpTime: number | null = null;
customMetrics: Array<{name: string, value: number}> = [];
constructor(
private performanceService: PerformanceService,
private webVitalsService: WebVitalsService
) {}
ngOnInit() {
this.webVitalsService.measureWebVitals();
this.fetchPerformanceMetrics();
// For demo purposes, we'll set some values
setTimeout(() => {
this.fcpTime = 1250;
this.lcpTime = 2800;
}, 2000);
}
fetchPerformanceMetrics() {
const metrics = this.performanceService.getMetrics();
this.customMetrics = Object.keys(metrics).map(key => ({
name: key,
value: metrics[key]
}));
}
formatMetricName(name: string): string {
return name
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());
}
getFCPClass(): string {
if (!this.fcpTime) return '';
if (this.fcpTime < 1000) return 'good';
if (this.fcpTime < 3000) return 'needs-improvement';
return 'poor';
}
getLCPClass(): string {
if (!this.lcpTime) return '';
if (this.lcpTime < 2500) return 'good';
if (this.lcpTime < 4000) return 'needs-improvement';
return 'poor';
}
}
Optimizing Based on Metrics
Once you've measured your performance metrics, you can take targeted actions to improve them:
Improving Load Time Metrics
- Code Splitting: Use Angular lazy loading to split your application into smaller chunks:
// app-routing.module.ts
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
},
{
path: 'user',
loadChildren: () => import('./user/user.module').then(m => m.UserModule)
}
];
- Preloading Strategies: Implement smart preloading for modules:
// app-routing.module.ts
import { PreloadAllModules } from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})],
exports: [RouterModule]
})
export class AppRoutingModule {}
Improving Runtime Performance Metrics
- OnPush Change Detection: Use
ChangeDetectionStrategy.OnPush
to optimize change detection:
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
@Input() product: Product;
}
- Trackby Function: Use
trackBy
with*ngFor
to improve rendering performance:
<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>
trackById(index: number, item: any): number {
return item.id;
}
- Pure Pipes: Use pure pipes instead of methods in templates:
@Pipe({
name: 'formatPrice',
pure: true
})
export class FormatPricePipe implements PipeTransform {
transform(price: number): string {
return `$${price.toFixed(2)}`;
}
}
Summary
Performance metrics are essential for ensuring your Angular application provides a great user experience. By measuring, monitoring, and optimizing these metrics, you can create applications that are fast to load and responsive to user interactions.
Key takeaways:
- Measure performance using built-in and custom tools
- Focus on both load time and runtime performance metrics
- Set performance budgets to prevent regressions
- Implement targeted optimizations based on measured data
- Make performance a continuous part of your development cycle
Additional Resources
- Angular Performance Documentation
- Web Vitals
- Lighthouse Performance Scoring
- Chrome DevTools Performance Analysis
Exercises
- Create a custom performance monitoring service that tracks the initialization time of each component in your application.
- Implement a performance budget in your Angular project and set up CI checks to ensure it's not exceeded.
- Identify and optimize the three most resource-intensive components in your application using the techniques covered in this guide.
- Create a dashboard that displays real-time performance metrics for your application, including Core Web Vitals.
- Benchmark your application before and after implementing the optimization techniques discussed in this guide, and document the improvements.
Happy coding and optimizing!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)