How to profile Angular performance

Profiling Angular applications identifies performance bottlenecks in rendering, change detection, and component initialization. With over 12 years of Angular development experience since 2014 and as the creator of CoreUI, I’ve optimized numerous slow Angular applications. Chrome DevTools and Angular DevTools provide profiling capabilities to measure component render times and change detection cycles. This approach helps you identify and fix performance issues before they impact users.

Use Chrome DevTools Performance tab and Angular DevTools Profiler to identify and fix Angular performance bottlenecks.

Chrome DevTools Performance profiling:

// app.component.ts
import { Component, OnInit } from '@angular/core'

@Component({
  selector: 'app-root',
  template: `
    <div>
      <h1>Performance Test</h1>
      <app-user-list [users]="users"></app-user-list>
    </div>
  `
})
export class AppComponent implements OnInit {
  users: any[] = []

  ngOnInit() {
    // Generate large dataset
    this.users = Array.from({ length: 1000 }, (_, i) => ({
      id: i,
      name: `User ${i}`,
      email: `user${i}@example.com`
    }))
  }
}

Profile in Chrome DevTools:

  1. Open Chrome DevTools (F12)
  2. Go to Performance tab
  3. Click Record button
  4. Interact with application
  5. Stop recording
  6. Analyze flame graph:
    • Yellow: JavaScript execution
    • Purple: Rendering
    • Green: Painting
    • Look for long tasks (>50ms)

Performance measurement API:

// performance.service.ts
import { Injectable } from '@angular/core'

@Injectable({
  providedIn: 'root'
})
export class PerformanceService {
  measureComponentInit(componentName: string, callback: () => void) {
    const measureName = `${componentName}-init`

    performance.mark(`${measureName}-start`)
    callback()
    performance.mark(`${measureName}-end`)

    performance.measure(
      measureName,
      `${measureName}-start`,
      `${measureName}-end`
    )

    const measure = performance.getEntriesByName(measureName)[0]
    console.log(`${componentName} initialization: ${measure.duration.toFixed(2)}ms`)
  }

  logPerformanceMetrics() {
    const entries = performance.getEntriesByType('measure')
    console.table(entries.map(entry => ({
      name: entry.name,
      duration: `${entry.duration.toFixed(2)}ms`
    })))
  }
}

// Using in component
@Component({
  selector: 'app-user-list',
  template: '<div>...</div>'
})
export class UserListComponent implements OnInit {
  constructor(private perfService: PerformanceService) {}

  ngOnInit() {
    this.perfService.measureComponentInit('UserListComponent', () => {
      this.loadUsers()
    })
  }

  loadUsers() {
    // Expensive operation
  }
}

Angular DevTools Profiler:

// Enable profiling in production (app.module.ts)
import { ApplicationRef } from '@angular/core'
import { enableDebugTools } from '@angular/platform-browser'

export class AppModule {
  constructor(appRef: ApplicationRef) {
    const componentRef = appRef.components[0]
    enableDebugTools(componentRef)
  }
}

Steps in Angular DevTools:

  1. Install Angular DevTools extension
  2. Open DevTools → Angular tab
  3. Go to Profiler tab
  4. Click “Record” button
  5. Interact with app
  6. Stop recording
  7. View:
    • Change detection cycles
    • Component render times
    • Bar chart of slowest components

Measure change detection:

// change-detection-profiler.ts
import { ApplicationRef, NgZone } from '@angular/core'
import { Injectable } from '@angular/core'

@Injectable({
  providedIn: 'root'
})
export class ChangeDetectionProfiler {
  private detectionCount = 0

  constructor(
    private appRef: ApplicationRef,
    private zone: NgZone
  ) {
    this.monitorChangeDetection()
  }

  private monitorChangeDetection() {
    this.zone.onStable.subscribe(() => {
      this.detectionCount++
      console.log(`Change detection cycles: ${this.detectionCount}`)
    })
  }

  logChangeDetectionStats() {
    console.log(`Total change detection cycles: ${this.detectionCount}`)
  }
}

Profile specific operations:

// data.component.ts
import { Component } from '@angular/core'

@Component({
  selector: 'app-data',
  template: '<div>{{ processedData }}</div>'
})
export class DataComponent {
  processedData: string

  processData() {
    console.time('Data Processing')

    // Expensive operation
    let result = ''
    for (let i = 0; i < 100000; i++) {
      result += String(i)
    }
    this.processedData = result

    console.timeEnd('Data Processing')
  }

  betterProcessData() {
    console.time('Optimized Processing')

    const parts: string[] = []
    for (let i = 0; i < 100000; i++) {
      parts.push(String(i))
    }
    this.processedData = parts.join('')

    console.timeEnd('Optimized Processing')
  }
}

Profile ngFor performance:

// list.component.ts
import { Component } from '@angular/core'

@Component({
  selector: 'app-list',
  template: `
    <div>
      <h3>Without trackBy (slow)</h3>
      <ul>
        <li *ngFor="let item of items">
          {{ item.name }}
        </li>
      </ul>

      <h3>With trackBy (fast)</h3>
      <ul>
        <li *ngFor="let item of items; trackBy: trackById">
          {{ item.name }}
        </li>
      </ul>
    </div>
  `
})
export class ListComponent {
  items = Array.from({ length: 1000 }, (_, i) => ({
    id: i,
    name: `Item ${i}`
  }))

  trackById(index: number, item: any) {
    return item.id
  }

  updateItems() {
    console.time('Update with trackBy')
    this.items = this.items.map(item => ({ ...item }))
    console.timeEnd('Update with trackBy')
  }
}

Measure pipe performance:

// expensive.pipe.ts
import { Pipe, PipeTransform } from '@angular/core'

@Pipe({
  name: 'expensive',
  pure: true // Important for performance
})
export class ExpensivePipe implements PipeTransform {
  transform(value: string): string {
    console.time('ExpensivePipe')

    // Expensive transformation
    let result = value
    for (let i = 0; i < 1000; i++) {
      result = result.toUpperCase().toLowerCase()
    }

    console.timeEnd('ExpensivePipe')
    return result
  }
}

// Better: Pure pipe + memoization
@Pipe({
  name: 'optimized',
  pure: true
})
export class OptimizedPipe implements PipeTransform {
  private cache = new Map<string, string>()

  transform(value: string): string {
    if (this.cache.has(value)) {
      return this.cache.get(value)!
    }

    const result = value.toUpperCase()
    this.cache.set(value, result)
    return result
  }
}

Runtime performance checks:

// performance-monitor.service.ts
import { Injectable } from '@angular/core'

@Injectable({
  providedIn: 'root'
})
export class PerformanceMonitor {
  monitorLongTasks() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.duration > 50) {
            console.warn(`Long task detected: ${entry.duration.toFixed(2)}ms`)
          }
        }
      })

      observer.observe({ entryTypes: ['longtask'] })
    }
  }

  getMemoryUsage() {
    if (performance.memory) {
      return {
        usedJSHeapSize: (performance.memory.usedJSHeapSize / 1048576).toFixed(2),
        totalJSHeapSize: (performance.memory.totalJSHeapSize / 1048576).toFixed(2),
        limit: (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2)
      }
    }
    return null
  }

  measureFPS() {
    let lastTime = performance.now()
    let frames = 0

    const measure = () => {
      frames++
      const currentTime = performance.now()

      if (currentTime >= lastTime + 1000) {
        const fps = Math.round((frames * 1000) / (currentTime - lastTime))
        console.log(`FPS: ${fps}`)
        frames = 0
        lastTime = currentTime
      }

      requestAnimationFrame(measure)
    }

    requestAnimationFrame(measure)
  }
}

Bundle size analysis:

# Analyze bundle size
ng build --stats-json

# Install webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer

# Analyze
npx webpack-bundle-analyzer dist/your-app/stats.json

Source map explorer:

# Install source-map-explorer
npm install --save-dev source-map-explorer

# Build with source maps
ng build --source-map

# Analyze
npx source-map-explorer dist/**/*.js

Lighthouse audit:

# Install Lighthouse CLI
npm install -g lighthouse

# Run audit
lighthouse http://localhost:4200 --view

# Or use Chrome DevTools Lighthouse tab

Best Practice Note

Use Chrome DevTools Performance tab to record and analyze application behavior during user interactions. Angular DevTools Profiler shows component-specific performance metrics and change detection cycles. Use console.time and console.timeEnd for quick performance checks during development. Enable production mode to see realistic performance—development mode includes extra checks. Use trackBy with ngFor to improve list rendering performance. Profile in production build, not development—optimizations are disabled in dev mode. This is how we profile CoreUI Angular applications—identifying bottlenecks in rendering, change detection, and bundle size to deliver fast, responsive dashboards for enterprise users.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team