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