npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@browsonic/angular

v2.14.3

Published

Angular adapter for @browsonic/sdk — ErrorHandler, BrowsonicService, standalone provideBrowsonic() factory. Apache-2.0.

Readme

@browsonic/angular

Angular adapter for @browsonic/sdkErrorHandler drop-in, BrowsonicService injectable, standalone provideBrowsonic() factory, Router instrumentation, HttpClient companion.

Status: stable, published to npm — check package.json or npm for the current version. Angular 17+ standalone style. Module-NgModule apps work via the same providers (see Quickstart — NgModule). Pure TypeScript: no Angular / RxJS runtime dependency in this package's bundle (@angular/core, @angular/router, @angular/common/http, and rxjs all stay peer-only type imports).

Install

npm install @browsonic/sdk @browsonic/angular

@browsonic/sdk and @angular/core (≥17) are peer dependencies.

Quickstart — Angular 17+ standalone

// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { ErrorHandler } from '@angular/core';
import { getBrowsonic } from '@browsonic/sdk';
import { provideBrowsonic, BrowsonicErrorHandler } from '@browsonic/angular';
import { AppComponent } from './app/app.component';

const sdk = getBrowsonic();
sdk.init({
  apiEndpoint: 'https://your-ingest-endpoint.test/v1/events',
  appKey: 'web',
  apiKey: 'pk_live_...', // publishable key — safe to ship in the browser
});

bootstrapApplication(AppComponent, {
  providers: [
    ...provideBrowsonic({ sdk }),
    { provide: ErrorHandler, useExisting: BrowsonicErrorHandler },
  ],
});

Inject the service anywhere

import { Component, inject } from '@angular/core';
import { BrowsonicService } from '@browsonic/angular';

@Component({ selector: 'app-buy', template: '<button (click)="buy()">Buy</button>' })
export class BuyComponent {
  private readonly browsonic = inject(BrowsonicService);

  buy(): void {
    try {
      // …purchase logic
    } catch (err) {
      this.browsonic.captureError(err as Error);
    }
  }
}

Quickstart — NgModule (Angular pre-standalone)

provideBrowsonic() is a standalone provider factory, but the values it returns are plain Provider[] — they work in AppModule.providers unchanged. Apps still on @NgModule({ ... }) bootstrap the same way:

// src/app/app.module.ts
import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { getBrowsonic } from '@browsonic/sdk';
import { provideBrowsonic, BrowsonicErrorHandler } from '@browsonic/angular';
import { AppComponent } from './app.component';

const sdk = getBrowsonic();
sdk.init({
  apiEndpoint: 'https://your-ingest-endpoint.test/v1/events',
  appKey: 'web',
  apiKey: 'pk_live_...', // publishable key — safe to ship in the browser
});

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [
    ...provideBrowsonic({ sdk }),
    { provide: ErrorHandler, useExisting: BrowsonicErrorHandler },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Same provider tokens, same BrowsonicService injection sites. The only difference is where the providers array lives — bootstrapApplication for standalone, AppModule.providers for NgModule.

Router instrumentation

installRouterInstrumentation(router, options?) subscribes to Router.events, filters for NavigationEnd (via the urlAfterRedirects structural discriminator), and emits a category: 'navigation' breadcrumb on every successful route change. Returns an unsubscribe() callable for HMR-friendly teardown.

import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { Router } from '@angular/router';
import { installRouterInstrumentation } from '@browsonic/angular';

@Component({ selector: 'app-root', template: '<router-outlet/>' })
export class AppComponent implements OnInit, OnDestroy {
  private readonly router = inject(Router);
  private off?: () => void;

  ngOnInit(): void {
    this.off = installRouterInstrumentation(this.router);
  }

  ngOnDestroy(): void {
    this.off?.();
  }
}

The structural RouterLike shape only requires an events Observable, so test doubles work without @angular/router.

Atlas page views with route templates

When page-view tracking is on, each NavigationEnd also calls trackPageView(template, { name, screen }) with:

  • the route template — the matched route tree's parameterized path (/users/:id), which the backend prefers over URL normalization;
  • the screen name — the deepest matched route's data['browsonicName'] (explicit) or data['title'] (the common Angular convention), feeding the SDK ≥ 3.13 screen-name channel;
  • the screen context — the deepest matched route's data['browsonicScreen'] ({ id, kind, zone }), feeding the SDK ≥ 3.17 Atlas screen-context channel.

The trackPageViews option defaults to the core's atlas: true umbrella on SDK ≥ 3.14 (feature-detected via isAtlasEnabled()): when the host initialized with atlas: true, template tracking turns on with no second flag and the core hands organic page views over on the first template — no double counting. On older cores the default stays false; setting trackPageViews: true explicitly keeps the legacy contract (pair it with manualPageViews: true in the SDK init there).

// Route declarations feed all three channels:
const routes: Routes = [
  {
    path: 'billing/invoices/:id',
    component: InvoiceDetailComponent,
    data: {
      browsonicName: 'Invoice Detail',
      browsonicScreen: { id: 'billing.invoice-detail', kind: 'detail', zone: 'billing' },
    },
  },
];

The snapshot walkers behind this are exported for hosts that call trackPageView themselves — see routeTemplateFromSnapshot, routeNameFromSnapshot, and routeScreenFromSnapshot.

HttpClient companion

createBrowsonicHttpReporter(options?) returns a (request, error) => void callback that captures HttpClient failures to the SDK. Wire it into your own HttpInterceptor so the adapter stays peer-only on @angular/common/http and rxjs:

// src/app/browsonic-http.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { createBrowsonicHttpReporter } from '@browsonic/angular';

@Injectable()
export class BrowsonicHttpInterceptor implements HttpInterceptor {
  private readonly report = createBrowsonicHttpReporter({
    // Skip Browsonic's own ingest URL so a failed report can't trigger
    // another report (would loop).
    ignoreUrls: [/\/v1\/events$/],
    // Suppress 401 / 404 — surfaced at the UI layer, not in the dashboard.
    ignoreStatuses: [401, 404],
  });

  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(req).pipe(
      catchError((err: unknown) => {
        this.report(req, err);
        return throwError(() => err);
      }),
    );
  }
}

Register it via HTTP_INTERCEPTORS — standalone or NgModule, both shapes work:

// Standalone (app.config.ts)
import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
providers: [
  provideHttpClient(withInterceptorsFromDi()),
  { provide: HTTP_INTERCEPTORS, useClass: BrowsonicHttpInterceptor, multi: true },
];

// NgModule (AppModule.providers)
import { HTTP_INTERCEPTORS } from '@angular/common/http';
providers: [{ provide: HTTP_INTERCEPTORS, useClass: BrowsonicHttpInterceptor, multi: true }];

The reporter tags the active scope with angular.http.method + angular.http.status, attaches httpUrl (capped at 256 chars) and a truncated httpResponseBody (maxBodyLength: 0 disables body capture entirely). See http-interceptor.ts for all options.

API

BrowsonicErrorHandler

Drop-in for Angular's ErrorHandler provider. Implements the framework's duck-typed handleError(error: unknown): void shape. Calls sdk.captureError, then forwards to console.error so the Angular default dev-tools experience is preserved (consoleFallback: false to disable).

BrowsonicService

Injectable wrapper around the SDK. Methods: setUser, clearUser, captureError, captureMessage, addBreadcrumb, setTag, getSdk.

provideBrowsonic(options?)

Standalone-style provider factory. Returns the providers array that registers BrowsonicErrorHandler and BrowsonicService as singletons. Wire the framework's ErrorHandler token to useExisting: BrowsonicErrorHandler separately so consumers control whether the handler also relays to a custom upstream handler (we don't chain by default — Angular's ErrorHandler is single-binding).

installRouterInstrumentation(router, options?)

Subscribes to a RouterLike.events Observable and emits a category: 'navigation' breadcrumb on every NavigationEnd. When page-view tracking is on (trackPageViews, defaulting to the core's atlas: true umbrella on SDK ≥ 3.14), each navigation also sends trackPageView with the route template, screen name, and screen context. Returns an unsubscribe() callable. Structural RouterLike / RouterEventLike types — no @angular/router runtime import. See Router instrumentation.

routeTemplateFromSnapshot(root)

Joins the matched route tree's routeConfig.path segments into the parameterized route template (/users/:id/orders) — the authoritative form the Atlas backend prefers over URL normalization. Walks the primary-outlet chain (depth-capped); empty-path levels (componentless / lazy wrappers) are skipped. Returns '' when nothing matched, so callers can omit the field and fall back to URL normalization.

routeNameFromSnapshot(root)

Returns the deepest matched route's data['browsonicName'] (explicit) or data['title'] (the common Angular convention) — a host-authored human name for the screen, feeding the SDK ≥ 3.13 screen-name channel. Returns '' when the route tree declares none.

routeScreenFromSnapshot(root)

Returns the deepest matched route's data['browsonicScreen'] declaration ({ id, kind, zone }, typed as RouteScreenContextLike) for the SDK ≥ 3.17 Atlas screen-context channel, or undefined when no level declares one. Deepest declaration wins wholesale (no per-field merging across levels). Only string fields are forwarded; the SDK normalizes and validates each field independently, so an invalid value never breaks the page view.

createBrowsonicHttpReporter(options?)

Returns a (request, error) => void callback that captures HttpClient failures. Filters by ignoreUrls (string or RegExp) and ignoreStatuses. Tags the scope with <ns>.method + <ns>.status, attaches httpUrl + truncated httpResponseBody. Designed to be wired into a consumer-owned HttpInterceptor so the adapter stays peer-only on @angular/common/http and rxjs. See HttpClient companion.

Defensive contract

  • The host app must never crash because reporting failed.
  • All SDK calls are wrapped in try { … } catch {}.
  • BrowsonicErrorHandler.handleError never throws — even when both the SDK and console.error raise, the method returns silently.
  • All forwarders are no-ops when the SDK is unreachable.

Optional /decorated entry-point — @Injectable + signals

If you don't mind @angular/core running at runtime in this package's bundle (rather than the default type-only contract), you can import from @browsonic/angular/decorated to get:

  • BrowsonicDecoratedService@Injectable({ providedIn: 'root' }) version of BrowsonicService. No more provider wiring; just inject(BrowsonicDecoratedService) from any component.
  • provideBrowsonicUserSignal(options?) — provider factory that bridges a WritableSignal<UserContext | null> to sdk.setUser. Set the signal to null to clear the user.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideBrowsonicUserSignal } from '@browsonic/angular/decorated';
import { provideBrowsonic, BrowsonicErrorHandler } from '@browsonic/angular';
import { ErrorHandler } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    ...provideBrowsonic({ sdk }),
    { provide: ErrorHandler, useExisting: BrowsonicErrorHandler },
    ...provideBrowsonicUserSignal({ initial: null }),
  ],
};

// any component
import { Component, inject } from '@angular/core';
import { BROWSONIC_USER_SIGNAL } from '@browsonic/angular/decorated';

@Component({ selector: 'app-login', template: '...' })
export class LoginComponent {
  private readonly user = inject(BROWSONIC_USER_SIGNAL);
  onLogin(profile: { id: string; email: string }) {
    this.user.set(profile);
  }
}

The default @browsonic/angular entry stays peer-only on @angular/core. Pick /decorated only if you want the decorator + signal ergonomics.

What this package does NOT do

  • NgZone-aware error capture. The default Angular zone catches errors; our ErrorHandler runs inside the zone. If your app uses NgZone.runOutsideAngular, errors there don't reach ErrorHandler — call browsonic.captureError(err) from your try/catch block manually.
  • Server-side rendering capture. Angular SSR runs in Node; the SDK is browser-only. Wire your own server-side telemetry.

License

Apache-2.0. See the repo root LICENSE and the package NOTICE.