For Angular Single Page Applications (SPAs), New Relic Browser Monitoring offers complete observability. Using the @newrelic/browser-agent NPM package, which offers a more manageable method than conventional script-based implementations, this article shows how to incorporate New Relic into an Angular application.

Before starting, ensure you have:

  • Angular Application (Angular 15+ recommended)
  • New Relic Account with Browser Monitoring enabled
  • New Relic Credentials:
  • Account ID
  • License Key (Browser monitoring license key)
  • Application ID
  • Agent ID
  • Trust Key

You can find these credentials in your New Relic account:
Go to Account Settings → API keys → Browser monitoring

Why Use NPM Package Instead of Script Tag?
Advantages:

  • TypeScript support with full type safety
  • Environment-based configuration management
  • Better integration with Angular's build system
  • Custom service layer for easier usage
  • Framework-specific features (SPA route tracking, HTTP interception)
  • Version control through package. json

Installation
Install the New Relic Browser Agent Package
npm install @newrelic/browse

Configuration
Add New Relic Configuration to Environment Files
export const environment = {
  newRelic: {
    enabled: true,
    accountID: 'YOUR_ACCOUNT_ID',
    trustKey: 'YOUR_TRUST_KEY',
    agentID: 'YOUR_AGENT_ID',
    licenseKey: 'YOUR_LICENSE_KEY',
    applicationID: 'YOUR_APPLICATION_ID'
  }
};


Create separate configurations for different environments (development, staging, production) with appropriate credentials.
Implementation

Step 1: Initialize New Relic in main.ts

The New Relic agent should be initialized after Angular bootstraps.
import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
import {NewRelicHttpInterceptor} from './app/global/services/newrelic-handler/newrelic-http.interceptor';

// ... other imports and providers

if (environment.production) {
  enableProdMode();
}

// Bootstrap Angular first
bootstrapApplication(AppComponent, {
  providers : [
    // ... your other providers
    provideHttpClient(withInterceptorsFromDi()),

    // NEW RELIC: Register HTTP Interceptor
    {
      provide : HTTP_INTERCEPTORS,
      useClass : NewRelicHttpInterceptor,
      multi : true
    }
  ]
})
    .then(() => {
      if (environment.newRelic?.enabled) {
        setTimeout(() => {
          try {
            // Capture native console functions before agent patches
            const nativeWarn = console.warn.bind(console);
            const nativeError = console.error.bind(console);

            // Initialize New Relic Browser Agent
            new BrowserAgent( {
              init : {
                distributed_tracing : { enabled : true },
                privacy : { cookies_enabled : true },
                ajax : { deny_list : [], enabled : true },
                session_trace : { enabled : true },
                session_replay : {
                  enabled : true,
                  sampling_rate : 10,
                  error_sampling_rate : 100
                },
                jserrors : {
                  enabled : true,
                  harvestConsoleErrors : false  // Don't capture console.error
                } as any,
                logging : {
                  enabled : true,
                  harvestConsoleErrors : false,
                  harvestConsoleWarns : false,
                  harvestConsoleInfo : false
                } as any,
                metrics : { enabled : true },
                page_action : { enabled : true }
              },
              info : {
                beacon : 'bam.nr-data.net',
                errorBeacon : 'bam.nr-data.net',
                licenseKey : environment.newRelic.licenseKey,
                applicationID : environment.newRelic.applicationID,
                sa : 1
              },
              loader_config : {
                accountID : environment.newRelic.accountID,
                trustKey : environment.newRelic.trustKey,
                agentID : environment.newRelic.agentID,
                licenseKey : environment.newRelic.licenseKey,
                applicationID : environment.newRelic.applicationID
              }
            });

            // Restore native console functions to prevent console logs from
            // being sent to New Relic
            console.warn = nativeWarn;
            console.error = nativeError;

            // Optional: Customize New Relic logging behavior
            const nr : any = (window as any).newrelic;
            if (nr?.log) {
              const originalLog = nr.log.bind(nr);
              nr.log = function(message: string, attributes ?: any) {
                const enhancedAttributes = { ... attributes };
                return originalLog(message, enhancedAttributes);
              };
            }
          } catch (error) {
            console.error('New Relic initialization failed:', error);
          }
        }, 100);
      }
    })
    .catch(err => console.log(err));

  • Initialize after Angular bootstrap to ensure proper timing
  • Capture and restore console functions to prevent console logs from being sent to New Relic
  • Wrap initialization in try-catch for error handling

Step 2: Create New Relic Service Wrapper
Create a service to wrap New Relic functionality: src/app/global/services/newrelic-handler/newrelic.service.ts
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class NewRelicService {
  private isInitialized = false;

  constructor() {
    // Check if New Relic is already initialized (from main.ts)
    if ((window as any).newrelic) {
      this.isInitialized = true;
    }
  }

  /**
   * Report custom error to New Relic
   * @param error - Error object
   * @param customAttributes - Additional attributes to track
   */
  noticeError(error: Error, customAttributes?: Record<string, any>): void {
    if (!this.isReady()) return;

    try {
      const attributes = {
        timestamp: new Date().toISOString(),
        errorName: error.name,
        errorMessage: error.message,
        errorStack: (error as any).originalStack || error.stack,
        userAgent: navigator.userAgent,
        url: window.location.href,
        ...customAttributes
      };
      const nr = (window as any).newrelic;

      if (nr.log) {
        Object.entries(attributes).forEach(([key, value]) => {
          if (value !== undefined && value !== null && typeof value !== 'object') {
            try {
              nr.setCustomAttribute(key, value);
            } catch {}
          }
        });
        nr.log(error.message || 'Error occurred', {
          level: 'ERROR',
          ...attributes
        });
      }
    } catch (e) {
      console.error('New Relic error reporting failed:', e);
    }
  }

  /**
   * Track custom user action/event
   * @param name - Name of the action
   * @param attributes - Custom attributes for the action
   */
  addPageAction(name: string, attributes?: Record<string, any>): void {
    if (!this.isReady()) return;

    try {
      (window as any).newrelic.addPageAction(name, {
        ...attributes,
        timestamp: new Date().toISOString(),
        url: window.location.href,
        userAgent: navigator.userAgent
      });
    } catch (e) {
      console.error('New Relic page action failed:', e);
    }
  }

  /**
   * Set custom attribute for the current session
   * @param name - Attribute name
   * @param value - Attribute value
   */
  setCustomAttribute(name: string, value: string | number | boolean): void {
    if (!this.isReady()) return;

    try {
      (window as any).newrelic.setCustomAttribute(name, value);
    } catch (e) {
      console.error('New Relic custom attribute failed:', e);
    }
  }

  /**
   * Set user ID for tracking
   * @param userId - Unique user identifier
   */
  setUserId(userId: string): void {
    this.setCustomAttribute('userId', userId);
    this.setCustomAttribute('enduser.id', userId);
  }

  /**
   * Set user information
   * @param userInfo - User information object
   */
  setUserInfo(userInfo: { userId: string; email?: string; name?: string; role?: string }): void {
    if (userInfo.userId) this.setUserId(userInfo.userId);
    if (userInfo.email) this.setCustomAttribute('userEmail', userInfo.email);
    if (userInfo.name) this.setCustomAttribute('userName', userInfo.name);
    if (userInfo.role) this.setCustomAttribute('userRole', userInfo.role);
  }

  /**
   * Track page view manually (useful for SPA)
   * @param pageName - Name of the page/route
   */
  setPageViewName(pageName: string): void {
    if (!this.isReady()) return;

    try {
      (window as any).newrelic.setPageViewName(pageName);
    } catch (e) {
      console.error('New Relic page view name failed:', e);
    }
  }

  /**
   * Add release version for tracking
   * @param version - Application version
   */
  setApplicationVersion(version: string): void {
    this.setCustomAttribute('applicationVersion', version);
    this.setCustomAttribute('release', version);
  }

  /**
   * Check if New Relic is initialized and ready with full API
   */
  isReady(): boolean {
    const nr = (window as any).newrelic;
    const isReady =
      !!nr &&
      typeof nr.addPageAction === 'function' &&
      typeof nr.noticeError === 'function';

    if (isReady && !this.isInitialized) {
      this.isInitialized = true;
    }
    return isReady;
  }

  /**
   * Track custom metric
   * @param metricName - Name of the metric
   * @param value - Metric value
   * @param unit - Unit of measurement (default: 'ms')
   */
  trackMetric(metricName: string, value: number, unit: string = 'ms'): void {
    this.addPageAction('CustomMetric', { metricName, value, unit });
  }
}

  • Provides a clean TypeScript interface to New Relic
  • Includes readiness checks before making API calls
  • Handles errors gracefully
  • Adds useful metadata automatically

Step 3: Create Router Tracking Service
For SPAs, tracking route changes is crucial. So, create: src/app/global/services/newrelic-handler/newrelic-router-tracker.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationStart, NavigationEnd, NavigationError, NavigationCancel, Event } from '@angular/router';
import { NewRelicService } from './newrelic.service';
import { filter } from 'rxjs/operators';

@Injectable({  providedIn: 'root'})
export class NewRelicRouterTrackerService {
  private navigationStartTime: number = 0;
  private currentUrl: string = '';
  private previousUrl: string = '';

  constructor(
    private router: Router,
    private newRelicService: NewRelicService
  ) {}

  /**
   * Start tracking Angular router navigation events
   */
  startTracking(): void {
    if (this.newRelicService.isReady()) {
      this.initializeTracking();
    } else {
      // Use polling with exponential backoff to wait for New Relic
      this.waitForNewRelic();
    }
  }

  /**
   * Wait for New Relic to be ready before initializing tracking
   * Uses polling with exponential backoff
   */
  private waitForNewRelic(attempt: number = 1, maxAttempts: number = 10): void {
    // Start with 100ms, increase with each attempt (100, 200, 400, 800, etc.)
    const delay = Math.min(100 * Math.pow(2, attempt - 1), 3000);

    console.log(`Waiting for New Relic to be ready (attempt ${attempt}/${maxAttempts})...`);

    setTimeout(() => {
      if (this.newRelicService.isReady()) {
        console.log('✓ New Relic is now ready, starting router tracking');
        this.initializeTracking();
      } else if (attempt < maxAttempts) {
        this.waitForNewRelic(attempt + 1, maxAttempts);
      } else {
        console.warn(`New Relic not ready after ${maxAttempts} attempts, router tracking disabled`);
      }
    }, delay);
  }

  /**
   * Initialize the actual tracking once New Relic is ready
   */
  private initializeTracking(): void {
    // Track navigation start
    this.router.events.pipe(
      filter((event: Event): event is NavigationStart => event instanceof NavigationStart)
    ).subscribe((event: NavigationStart) => {
      this.navigationStartTime = performance.now();
      this.previousUrl = this.currentUrl;
      this.currentUrl = event.url;

      this.newRelicService.addPageAction('RouteChangeStart', {
        url: event.url,
        previousUrl: this.previousUrl,
        navigationTrigger: event.navigationTrigger,
        restoredState: event.restoredState ? 'yes' : 'no'
      });
    });

    // Track navigation end (success)
    this.router.events.pipe(
      filter((event: Event): event is NavigationEnd => event instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      const duration = performance.now() - this.navigationStartTime;

      this.newRelicService.addPageAction('RouteChangeComplete', {
        url: event.urlAfterRedirects,
        previousUrl: this.previousUrl,
        duration: Math.round(duration),
        status: 'success'
      });

      // Set page view name for better tracking in New Relic
      const pageName = this.extractPageName(event.urlAfterRedirects);
      this.newRelicService.setPageViewName(pageName);

      // Track as successful route change metric
      this.newRelicService.trackMetric('RouteChangeDuration', duration, 'ms');
    });

    // Track navigation errors
    this.router.events.pipe(
      filter((event: Event): event is NavigationError => event instanceof NavigationError)
    ).subscribe((event: NavigationError) => {
      const duration = performance.now() - this.navigationStartTime;

      this.newRelicService.addPageAction('RouteChangeError', {
        url: event.url,
        previousUrl: this.previousUrl,
        duration: Math.round(duration),
        status: 'error',
        errorMessage: event.error?.message || 'Unknown navigation error'
      });

      // Report as error to New Relic
      const error = new Error(`Navigation Error: ${event.error?.message || 'Unknown error'}`);
      this.newRelicService.noticeError(error, {
        errorType: 'NavigationError',
        url: event.url,
        previousUrl: this.previousUrl
      });
    });

    // Track navigation cancel
    this.router.events.pipe(
      filter((event: Event): event is NavigationCancel => event instanceof NavigationCancel)
    ).subscribe((event: NavigationCancel) => {
      const duration = performance.now() - this.navigationStartTime;

      this.newRelicService.addPageAction('RouteChangeCancel', {
        url: event.url,
        previousUrl: this.previousUrl,
        duration: Math.round(duration),
        status: 'cancelled',
        reason: event.reason
      });
    });

    console.log('✓ New Relic Router Tracking started');
  }

  /**
   * Extract a clean page name from URL
   * @param url - Full URL path
   */
  private extractPageName(url: string): string {
    // Remove query parameters and fragments
    let cleanUrl = url.split('?')[0].split('#')[0];

    // Remove leading slash
    if (cleanUrl.startsWith('/')) {
      cleanUrl = cleanUrl.substring(1);
    }

    // If empty, it's the home page
    if (!cleanUrl) {
      return 'Home';
    }

    // Replace slashes with dots and capitalize
    const pageName = cleanUrl
      .split('/')
      .map(part => part.charAt(0).toUpperCase() + part.slice(1))
      .join('.');

    return pageName;
  }

  /**
   * Track specific route manually
   * @param routeName - Name of the route
   * @param metadata - Additional metadata
   */
  trackRouteManually(routeName: string, metadata?: Record<string, any>): void {
    this.newRelicService.addPageAction('ManualRouteTrack', {
      routeName,
      url: window.location.href,
      ...metadata
    });
  }
}

  • Handles timing issues with exponential backoff polling
  • Tracks all router events (start, end, error, cancel)
  • Measures navigation duration
  • Extracts clean page names for better dashboard organization

Step 4: Create HTTP Interceptor
Track API calls automatically: src/app/global/services/newrelic-handler/newrelic-http.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { NewRelicService } from './newrelic.service';

@Injectable()
export class NewRelicHttpInterceptor implements HttpInterceptor {
  constructor(private newRelicService: NewRelicService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!this.newRelicService.isReady()) {
      return next.handle(req);
    }

    const startTime = performance.now();
    const requestDetails = {
      url: req.url,
      method: req.method,
      urlWithParams: req.urlWithParams
    };

    return next.handle(req).pipe(
      tap(
        event => {
          if (event instanceof HttpResponse) {
            const duration = Math.round(performance.now() - startTime);

            this.newRelicService.addPageAction('APICallSuccess', {
              ...requestDetails,
              statusCode: event.status,
              statusText: event.statusText,
              duration,
              responseType: event.type,
              contentType: event.headers.get('content-type') || 'unknown'
            });

            this.newRelicService.trackMetric(`API_${req.method}_Duration`, duration);
          }
        },
        error => {
          if (error instanceof HttpErrorResponse) {
            const duration = Math.round(performance.now() - startTime);

            this.newRelicService.addPageAction('APICallFailure', {
              ...requestDetails,
              statusCode: error.status,
              statusText: error.statusText,
              errorMessage: error.message,
              errorName: error.name,
              duration
            });

            const errorObj = new Error(`API Error`);

            this.newRelicService.noticeError(errorObj, {
              errorType: 'HttpError',
              httpMethod: req.method,
              apiUrl: req.url,
              apiEndpoint: new URL(req.url).pathname,
              statusCode: error.status,
              statusText: error.statusText,
              errorMessage: error.message,
              serverErrorMessage: error.error?.error || error.error?.message || error.error,
              duration,
              pageUrl: window.location.href,
              pagePath: window.location.pathname,
              timestamp: new Date().toISOString()
            });
          }
        }
      )
    );
  }
}

  • Automatically tracks all HTTP requests
  • Measures API call duration
  • Captures success and failure scenarios
  • Provides detailed error information

Step 5: Initialize Router Tracking in AppComponent
Start router tracking in your root component: src/app/app.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { NewRelicRouterTrackerService } from './global/services/newrelic-handler/newrelic-router-tracker.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private newRelicRouterTracker: NewRelicRouterTrackerService
  ) {}

  ngOnInit() {
    // ... other initialization code

    // Start New Relic router tracking
    this.startNewRelicRouterTracking();
  }

  /**
   * Start New Relic Router Tracking
   */
  private startNewRelicRouterTracking(): void {
    try {
      // Start router tracking for SPA navigation
      this.newRelicRouterTracker.startTracking();
    } catch (error) {
      // Silently fail if New Relic is not available
      console.debug('New Relic router tracking not started:', error);
    }
  }
}

Advanced Features
User Identification
Track user sessions with user information:
import { NewRelicService } from './global/services/newrelic-handler/newrelic.service';

constructor(private newRelicService: NewRelicService) {}

onUserLogin(user: any) {
  this.newRelicService.setUserInfo({
    userId: user.id,
    email: user.email,
    name: user.name,
    role: user.role
  });
}

Custom Events
Track custom business events:
// Track a button click
this.newRelicService.addPageAction('ButtonClick', {
  buttonName: 'SubmitForm',
  formType: 'Contact',
  timestamp: new Date().toISOString()
});

// Track a purchase
this.newRelicService.addPageAction('Purchase', {
  productId: '123',
  amount: 99.99,
  currency: 'USD'
});

Application Version Tracking
Track application versions for release management:
this.newRelicService.setApplicationVersion('1.2.3');

Performance Considerations

  • New Relic data is batched and sent asynchronously
  • The agent has minimal performance impact
  • Use custom attributes sparingly to avoid payload size issues