Skip to main content

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:

typescript
// 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:

  1. Open Chrome DevTools (F12)
  2. Navigate to the "Lighthouse" tab
  3. Select "Performance" and click "Generate report"

Angular Performance Profiler

Angular provides a built-in profiler to measure change detection performance:

typescript
// 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:

typescript
// 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:

typescript
// 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:

json
{
"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:

  1. Largest Contentful Paint (LCP): Measures loading performance
  2. First Input Delay (FID): Measures interactivity
  3. Cumulative Layout Shift (CLS): Measures visual stability

To monitor Core Web Vitals in your Angular application:

typescript
// 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:

typescript
// 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

  1. Code Splitting: Use Angular lazy loading to split your application into smaller chunks:
typescript
// 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)
}
];
  1. Preloading Strategies: Implement smart preloading for modules:
typescript
// 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

  1. OnPush Change Detection: Use ChangeDetectionStrategy.OnPush to optimize change detection:
typescript
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
@Input() product: Product;
}
  1. Trackby Function: Use trackBy with *ngFor to improve rendering performance:
html
<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>
typescript
trackById(index: number, item: any): number {
return item.id;
}
  1. Pure Pipes: Use pure pipes instead of methods in templates:
typescript
@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:

  1. Measure performance using built-in and custom tools
  2. Focus on both load time and runtime performance metrics
  3. Set performance budgets to prevent regressions
  4. Implement targeted optimizations based on measured data
  5. Make performance a continuous part of your development cycle

Additional Resources

Exercises

  1. Create a custom performance monitoring service that tracks the initialization time of each component in your application.
  2. Implement a performance budget in your Angular project and set up CI checks to ensure it's not exceeded.
  3. Identify and optimize the three most resource-intensive components in your application using the techniques covered in this guide.
  4. Create a dashboard that displays real-time performance metrics for your application, including Core Web Vitals.
  5. 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! :)