---
title: "How to add product analytics to an Angular app"
description: "Add Amplitude to an Angular app from main.ts, track page views on router navigation, identify signed-in users with signals, and confirm events land."
canonical: "https://amplitude.com/en-us/build/angular-product-analytics"
---

# How to add product analytics to an Angular app

Add Amplitude to an Angular app from main.ts, track page views on router navigation, identify signed-in users with signals, and confirm events land.

Source: https://amplitude.com/en-us/build/angular-product-analytics

---

# How to add product analytics to an Angular app

Install the Amplitude Unified SDK in an Angular app from \`main.ts\`, capture page views on every Angular Router navigation, identify signed-in users from a signal, and track one custom event. About 10 minutes, all copy-paste.

- Angular 19+
- Unified SDK 1.x
- 10 min

Chris Tam · Last updated 14 October 2026

Angular is a TypeScript framework for building web apps, with its own router, dependency injection, and signals built in. [Product analytics](https://amplitude.com/amplitude-analytics) shows you what people do once your app loads: which routes they visit, what they use, and where they drop off.

In this tutorial, you'll add Amplitude to an Angular app from `main.ts`, capture page views, identify signed-in users, and track your first custom event.

## What you'll build

An Angular app that initializes Amplitude once in the browser, sends `[Amplitude] Page Viewed` on the first load and on every Angular Router navigation, attaches the signed-in user's ID and `plan` to their events, clears identity on sign-out, and tracks a `Project Created` event with a `template` property. It works with and without Angular SSR. By the end you'll see live events in Amplitude's event stream.

## Prerequisites

- An Angular 19 or later app with standalone components, for example one created with `ng new`. Angular SSR (`@angular/ssr`) is optional; every file here works with and without it. Angular 19 apps created with `--ssr` use `CommonEngine` unless you chose server routing. Apps with or without Zone.js both work.
- Code uses the Angular 20 file names (`app.ts`, `App`); on Angular 19 use `app.component.ts` and `AppComponent`.
- An auth setup that exposes the signed-in user as a signal with an `id` and a `plan`. Step 4 includes a small stand-in `AuthService` you can replace with your own.
- An Amplitude project API key. In Amplitude, go to Settings > Projects > your project > General.

No Amplitude account yet? A free account is enough for this tutorial; nothing here needs a paid plan. [Sign up for free](https://app.amplitude.com/signup) to get your project API key.

## 1. Install the SDK and add the key

[The Quickstart](https://amplitude.com/docs/get-started/amplitude-quickstart) recommends `@amplitude/unified` for browser apps installed from npm. It bundles analytics, session replay, Feature Experiment, and guides and surveys behind one init call.

Terminal

```sh
npm install @amplitude/unified
ng generate environments
```

`ng generate environments` creates `src/environments/environment.ts` and `src/environments/environment.development.ts`, and adds a `fileReplacements` entry to the development configuration in `angular.json`. `ng serve` uses the development configuration, so it reads `environment.development.ts`; `ng build` uses the production configuration, so it reads `environment.ts`.

The project API key is public and its scope is limited to the minimum needed to ingest data, so shipping it in the browser bundle is expected. Angular has no runtime environment variables in the browser by default, so the key goes into the environment files and gets compiled into the build. Committing it is fine. If you'd rather keep it out of git, have your CI write the key into the environment file before `ng build`.

src/environments/environment.ts

```typescript
// Keep lines other Angular pages added here: amplitudeDeploymentKey (/build/angular-feature-flags).
export const environment = {
  production: true,
  amplitudeApiKey: 'your-project-api-key',
};
```

src/environments/environment.development.ts

```typescript
// Keep lines other Angular pages added here: amplitudeDeploymentKey (/build/angular-feature-flags).
export const environment = {
  production: false,
  amplitudeApiKey: 'your-project-api-key',
};
```

Use a separate Amplitude project's key in `environment.development.ts` if you want to keep local testing out of your production data.

## 2. Initialize in main.ts

Put the SDK setup in one small module that exports `initAmplitude()`, then call it in `src/main.ts` before `bootstrapApplication`.

src/app/amplitude.ts

```typescript
// Keep lines other Angular pages added here: debugMode and privacyConfig
// (/build/angular-session-replay); pageViews: { trackHistoryChanges: 'pathOnly' }
// (/build/angular-web-analytics); the setAmplitudeReady(...) wrapper, its import and the
// setAmplitudeReady(null) line (/build/angular-feature-flags, /build/angular-guides-surveys);
// the experiment block and any analytics.deviceId handoff (/build/angular-feature-flags);
// the WebExperimentGlobal type and the amplitude.add(webExperiment.plugin()) block
// (/build/angular-web-experiment).
import * as amplitude from '@amplitude/unified';
import { environment } from '../environments/environment';

export function initAmplitude(): void {
  if (!environment.amplitudeApiKey) {
    console.warn('amplitudeApiKey is not set; analytics is off.');
    return;
  }

  // Nothing on this page needs to wait for initAll to finish.
  void amplitude.initAll(environment.amplitudeApiKey, {
    analytics: {
      autocapture: {
        attribution: true,
        pageViews: true,
        sessions: true,
        formInteractions: true,
        fileDownloads: true,
        elementInteractions: true,
      },
    sessionReplay: {
      // Every session while you build; about 1% in production.
      sampleRate: environment.production ? 0.01 : 1,
    },
  });
}
```

Later Angular pages add lines to this file and to the other files on this page; the comment at the top of each block names them. Already followed another Angular page? Keep what it added and merge this page's code in instead of replacing the file.

src/main.ts

```typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
// Angular 19 and earlier: src/app/app.component.ts, class AppComponent.
import { App } from './app/app';
import { initAmplitude } from './app/amplitude';

// Browser only, once per page load, before Angular starts.
initAmplitude();

bootstrapApplication(App, appConfig).catch((err) => console.error(err));
```

Keep your existing `main.ts` imports; on Angular 19 the root component import is `AppComponent` from `./app/app.component`.

**Why.** `main.ts` is the browser entry point. It runs once per page load, and with Angular SSR the server renders from `main.server.ts` instead, so the SDK never starts on the server. Calling `initAmplitude()` before `bootstrapApplication` also keeps it outside the Angular zone, which bootstrap hasn't created yet. The Browser SDK docs ask apps that use Zone.js to initialize outside the Angular zone, because the zone's patched DOM functions can break user interactions when autocapture calls them. Initializing from a component's constructor or `ngOnInit` would run inside the zone, after the first render, and on the server too. Zoneless apps don't have the zone problem, and the same `main.ts` works for them.

Clicks (`elementInteractions`) default to off in autocapture, which is why the config names it explicitly. `@amplitude/unified` turns on session replay when `sessionReplay.sampleRate` is above `0`. This config records every session under `ng serve` and about 1% in a production build, which is where Amplitude recommends starting; raise it over a few days. Your session replay capture quota depends on your plan; [compare plans](https://amplitude.com/pricing). Set `sampleRate` to `0` to turn session replay off. To mask sensitive elements before you record real users, see [the Angular session replay page](/build/angular-session-replay). For the full option list, see the [Browser Unified SDK reference](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk).

## 3. Let autocapture handle navigation

There's no code in this step. Angular Router changes the URL with the browser's History API on every navigation. With `pageViews: true`, the SDK sends `[Amplitude] Page Viewed` on init and again on every URL change, which covers `routerLink` clicks and `router.navigate()`. Each event carries the page path, title, and URL.

Don't subscribe to `Router` events to call `track('Page Viewed')`. It produces a second, differently named page view on every navigation.

If your app uses hash routing (`provideRouter(routes, withHashLocation())`), keep `pageViews: true`. The route lives after the `#`, and the default setting tracks hash changes. Don't set `trackHistoryChanges: 'pathOnly'`, which ignores the hash and stops route page views. [How to add web analytics to an Angular app](/build/angular-web-analytics) covers routing modes and attribution in detail.

## 4. Identify the signed-in user

Until you set a user ID, events belong to an anonymous device ID. Set it as soon as your auth state resolves, and reset only when someone signs out, so the next person on the same browser isn't merged into the last one.

If you don't have auth yet, use this stand-in. If you do, keep your own service and make sure it exposes the user as a signal. An auth library that gives you an observable converts with `toSignal(user$, { initialValue: null })`.

src/app/auth.service.ts

```typescript
// Keep lines other Angular pages added here: closeAll() before reset()
// (/build/angular-guides-surveys), experiment()?.clear() and refetch() after
// reset() (/build/angular-feature-flags).
import { Injectable, signal } from '@angular/core';
import * as amplitude from '@amplitude/unified';

export interface User {
  id: string;
  plan?: string;
}

// Stand-in for your auth. Replace the bodies with your own logic.
@Injectable({ providedIn: 'root' })
export class AuthService {
  readonly user = signal<User | null>(null);

  signIn(user: User): void {
    // ... your sign-in logic
    this.user.set(user);
  }

  async signOut(): Promise<void> {
    // ... your sign-out logic
    this.user.set(null);
    // Clears the user ID and issues a new device ID for the next person.
    amplitude.reset();
  }
```

If you keep your own auth, add the `amplitude.reset()` line to your sign-out method, after sign-out succeeds.

Then identify from a service that reacts to the user signal:

src/app/amplitude-identity.service.ts

```typescript
import { Injectable, PLATFORM_ID, computed, effect, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import * as amplitude from '@amplitude/unified';
import { AuthService } from './auth.service';

@Injectable({ providedIn: 'root' })
export class AmplitudeIdentityService {
  private readonly auth = inject(AuthService);
  private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));

  // Only the ID and plan matter. A new user object with the same
  // values doesn't trigger another Identify.
  private readonly identity = computed(
    () => {
      const user = this.auth.user();
      return user ? { id: user.id, plan: user.plan ?? 'unknown' } : null;
    },
    { equal: (a, b) => a?.id === b?.id && a?.plan === b?.plan },
  );

  constructor() {
    // Angular runs effects during server rendering too.
    if (!this.isBrowser) return;

    effect(() => {
      const identity = this.identity();

      // Signed out or not signed in yet: keep the same device ID.
      // Reset only from your sign-out method.
      if (!identity) return;

      // Amplitude drops IDs shorter than 5 characters. Use the same
      // string your backend sends as user_id.
      amplitude.setUserId(`user-${identity.id}`);

      const identify = new amplitude.Identify();
      identify.set('plan', identity.plan);
      amplitude.identify(identify);
    });
  }
```

Create the service once from the root component, which also gets the sign-in and sign-out buttons and links to the two routes from step 5:

src/app/app.ts

```typescript
// Angular 19 and earlier: src/app/app.component.ts, class AppComponent.
// Keep lines other Angular pages added here: nav links (/build/angular-session-replay,
// /build/angular-heatmaps), EngagementBridgeService and <app-feedback-button />
// (/build/angular-guides-surveys), ServerFlagsService (/build/angular-feature-flags).
import { Component, inject } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import { AmplitudeIdentityService } from './amplitude-identity.service';
import { AuthService } from './auth.service';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, RouterLink],
  template: `
    <nav>
      <a routerLink="/">Home</a>
      <a routerLink="/projects/new">New project</a>
      @if (auth.user()) {
        <button type="button" (click)="auth.signOut()">Sign out</button>
      } @else {
        <button type="button" (click)="auth.signIn({ id: '42', plan: 'pro' })">
          Sign in as test user
        </button>
      }
    </nav>
    <router-outlet />
  `,
})
export class App {
  protected readonly auth = inject(AuthService);

  constructor() {
    // Creating the service starts its identity effect.
    inject(AmplitudeIdentityService);
  }
```

This inline template replaces the starter page that `ng new` puts in `app.html`. If your root component already has markup, keep it and add the `inject(AmplitudeIdentityService)` constructor and your own sign-out button. Replace the test sign-in button with your real sign-in flow. Delete or update the generated `app.spec.ts` (`app.component.spec.ts` on Angular 19), which tests the starter template.

**Why.** Angular runs effects during server rendering as well as in the browser, so the `isPlatformBrowser` check keeps every SDK call out of the server render. The `computed` with a custom `equal` passes the effect only the ID and plan, so a refreshed user object with the same values doesn't send an extra Identify. A `null` user also describes every anonymous visitor on every page load, and `reset()` issues a new device ID each time it runs, so resetting there splits one visitor into many and breaks anything bucketed by device ID, such as feature flags and Web Experiment. The `plan` value updates the profile right away, but only shows in charts from the user's next event.

## 5. Track one custom event

Autocapture covers page views, sessions, forms, and clicks. Track the events that mean something to your product yourself, from a click handler, with properties you'll want to break down by.

src/app/projects/new-project.ts

```typescript
// Keep lines other Angular pages added here: /build/angular-feature-flags replaces this
// file with a flag-driven version; if you followed it, keep that version.
import { Component, signal } from '@angular/core';
import * as amplitude from '@amplitude/unified';

@Component({
  selector: 'app-new-project',
  template: `
    <select #picker (change)="template.set(picker.value)">
      <option value="blank">Blank</option>
      <option value="kanban">Kanban</option>
    </select>
    <button type="button" (click)="create()">Create project</button>
  `,
})
export class NewProject {
  protected readonly template = signal('blank');

  protected create(): void {
    // ... your create logic

    amplitude.track('Project Created', { template: this.template() });
  }
```

Add a home page and both routes, so you have something to navigate between:

src/app/home.ts

```typescript
// Keep lines other Angular pages added here: data-exp attributes
// (/build/angular-web-experiment), data-guide attributes (/build/angular-guides-surveys).
import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `<h1>Home</h1>`,
})
export class Home {}
```

src/app/app.routes.ts

```typescript
// Keep lines other Angular pages added here: their routes, such as team and billing
// (/build/angular-session-replay), projects/:projectId and settings (/build/angular-heatmaps),
// and pricing, welcome, login, account, plans and offer/:code (/build/angular-web-analytics),
// and the title on each route if you added titles there.
import { Routes } from '@angular/router';
import { Home } from './home';
import { NewProject } from './projects/new-project';

export const routes: Routes = [
  { path: '', component: Home },
  { path: 'projects/new', component: NewProject },
];
```

If `app.routes.ts` already has routes, including ones other Angular pages added, merge these two into the existing array instead of replacing it.

Click handlers only run after a click in the browser, so this is safe on a server-rendered page. Don't call `track` from a constructor or `ngOnInit`, which also run during server rendering. For events your server should send, such as a payment confirmed by your API, use the Node.js SDK there; [the Node.js product analytics page](/build/nodejs-product-analytics) covers identity and flushing.

**MCP.** Working in Cursor or Claude Code? Amplitude's MCP server lets your agent read your project's existing event taxonomy before it writes `track` calls, so new events match names you already use instead of inventing `projectCreated` next to `Project Created`. [Set up the MCP server](https://amplitude.com/docs/amplitude-ai/amplitude-mcp).

## Verify it worked

Run `ng serve`, open `http://localhost:4200`, click **Sign in as test user**, go to New project, create a project, then go back to Home. Check in this order:

1. **Network tab.** Filter for `api2.amplitude.com`. You should see a `POST` to `/2/httpapi` returning `200` within about a second of each navigation. With Angular SSR, Amplitude requests start only after the page loads in the browser; the server render sends none.
2. **Ingestion Debugger.** In Amplitude, click Data, select Sources, then open the Ingestion Debugger tab. The successful requests chart should show your uploads. It shows counts, not individual events.
3. **User lookup.** Go to Users > User Profiles and search `user-42`. The event stream shows `[Amplitude] Page Viewed` for each route and `Project Created` with its `template` property within ten seconds to a minute, and the profile shows `plan`. Events from before you signed in belong to the same device ID.

To watch payloads in the console, add `logLevel: amplitude.Types.LogLevel.Debug` to the `analytics` block in development.

## Troubleshooting

### No requests in the Network tab

The key is empty, so the warning from step 2 is in the console. `ng serve` reads `environment.development.ts` and `ng build` reads `environment.ts`, so check the file your configuration uses. If neither file exists, run `ng generate environments` again and check `fileReplacements` in `angular.json`.

### Clicks or form inputs stop working after you add Amplitude

`initAll` ran inside the Angular zone, for example from a component, a service, or `provideAppInitializer`. Call `initAmplitude()` from `src/main.ts` before `bootstrapApplication`. If it has to run inside Angular, wrap it: `inject(NgZone).runOutsideAngular(() => initAmplitude())`.

### "window is not defined" or "document is not defined" during server rendering

Something calls the SDK on the server: `initAmplitude()` imported into `app.config.ts` or a component, a `track` call in a constructor or `ngOnInit`, or an identity effect without the `isPlatformBrowser` check. Keep `initAmplitude()` in `main.ts`, identity in `AmplitudeIdentityService`, and `track` in click handlers.

### Two page view events per navigation

A `Router` events subscription or a page view helper is running alongside autocapture. Delete it. If both events are `[Amplitude] Page Viewed`, check that `initAmplitude()` is only called from `src/main.ts`.

### Signed-in events still show as anonymous

`AmplitudeIdentityService` never gets created, or it can't see the user. Check that the `App` constructor injects it and that your auth exposes the user as a signal; an observable needs `toSignal()` first.

### Production records every session

`environment.ts` is missing `production: true`, so `environment.production` is `undefined` and the sample rate falls through to `1`. Add the field to both environment files.

### Events send but never appear in the project

EU projects need `serverZone: 'EU'` at the top level of the `initAll` options and an API key from the EU project.

### Autocapture does something different from the code

Remote configuration is on by default, so autocapture settings saved in Amplitude under Data > Settings > Autocapture take precedence over the `autocapture` block in `src/app/amplitude.ts`. To keep autocapture under code control, add `remoteConfig: { fetchRemoteConfig: false }` to the `analytics` block.

## Full code reference

Every file from this tutorial in one block, the version to hand to an agent. 10 files. Merge the routes into your existing array, and on Angular 19 put the `App` code in `app.component.ts` with the class `AppComponent`.

10 files, 207 lines

```typescript
// -- src/environments/environment.ts --
// Keep lines other Angular pages added here: amplitudeDeploymentKey (/build/angular-feature-flags).
export const environment = {
  production: true,
  amplitudeApiKey: 'your-project-api-key',
};

// -- src/environments/environment.development.ts --
// Keep lines other Angular pages added here: amplitudeDeploymentKey (/build/angular-feature-flags).
export const environment = {
  production: false,
  amplitudeApiKey: 'your-project-api-key',
};

// -- src/app/amplitude.ts --
// Keep lines other Angular pages added here: debugMode and privacyConfig
// (/build/angular-session-replay); pageViews: { trackHistoryChanges: 'pathOnly' }
// (/build/angular-web-analytics); the setAmplitudeReady(...) wrapper, its import and the
// setAmplitudeReady(null) line (/build/angular-feature-flags, /build/angular-guides-surveys);
// the experiment block and any analytics.deviceId handoff (/build/angular-feature-flags);
// the WebExperimentGlobal type and the amplitude.add(webExperiment.plugin()) block
// (/build/angular-web-experiment).
import * as amplitude from '@amplitude/unified';
import { environment } from '../environments/environment';

export function initAmplitude(): void {
  if (!environment.amplitudeApiKey) {
    console.warn('amplitudeApiKey is not set; analytics is off.');
    return;
  }

  void amplitude.initAll(environment.amplitudeApiKey, {
    analytics: {
      autocapture: {
        attribution: true,
        pageViews: true,
        sessions: true,
        formInteractions: true,
        fileDownloads: true,
        elementInteractions: true,
      },
    sessionReplay: {
      sampleRate: environment.production ? 0.01 : 1,
    },
  });
}

// -- src/main.ts --
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
// Angular 19 and earlier: src/app/app.component.ts, class AppComponent.
import { App } from './app/app';
import { initAmplitude } from './app/amplitude';

initAmplitude();

bootstrapApplication(App, appConfig).catch((err) => console.error(err));

// -- src/app/auth.service.ts --
// Keep lines other Angular pages added here: closeAll() before reset()
// (/build/angular-guides-surveys), experiment()?.clear() and refetch() after
// reset() (/build/angular-feature-flags).
import { Injectable, signal } from '@angular/core';
import * as amplitude from '@amplitude/unified';

export interface User {
  id: string;
  plan?: string;
}

@Injectable({ providedIn: 'root' })
export class AuthService {
  readonly user = signal<User | null>(null);

  signIn(user: User): void {
    // ... your sign-in logic
    this.user.set(user);
  }

  async signOut(): Promise<void> {
    // ... your sign-out logic
    this.user.set(null);
    amplitude.reset();
  }

// -- src/app/amplitude-identity.service.ts --
import { Injectable, PLATFORM_ID, computed, effect, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import * as amplitude from '@amplitude/unified';
import { AuthService } from './auth.service';

@Injectable({ providedIn: 'root' })
export class AmplitudeIdentityService {
  private readonly auth = inject(AuthService);
  private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));

  private readonly identity = computed(
    () => {
      const user = this.auth.user();
      return user ? { id: user.id, plan: user.plan ?? 'unknown' } : null;
    },
    { equal: (a, b) => a?.id === b?.id && a?.plan === b?.plan },
  );

  constructor() {
    if (!this.isBrowser) return;

    effect(() => {
      const identity = this.identity();
      if (!identity) return;
      // Amplitude drops IDs shorter than 5 characters.
      amplitude.setUserId(`user-${identity.id}`);
      const identify = new amplitude.Identify();
      identify.set('plan', identity.plan);
      amplitude.identify(identify);
    });
  }

// -- src/app/app.ts --
// Angular 19 and earlier: src/app/app.component.ts, class AppComponent.
// Keep lines other Angular pages added here: nav links (/build/angular-session-replay,
// /build/angular-heatmaps), EngagementBridgeService and <app-feedback-button />
// (/build/angular-guides-surveys), ServerFlagsService (/build/angular-feature-flags).
import { Component, inject } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import { AmplitudeIdentityService } from './amplitude-identity.service';
import { AuthService } from './auth.service';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, RouterLink],
  template: `
    <nav>
      <a routerLink="/">Home</a>
      <a routerLink="/projects/new">New project</a>
      @if (auth.user()) {
        <button type="button" (click)="auth.signOut()">Sign out</button>
      } @else {
        <button type="button" (click)="auth.signIn({ id: '42', plan: 'pro' })">
          Sign in as test user
        </button>
      }
    </nav>
    <router-outlet />
  `,
})
export class App {
  protected readonly auth = inject(AuthService);

  constructor() {
    inject(AmplitudeIdentityService);
  }

// -- src/app/home.ts --
// Keep lines other Angular pages added here: data-exp attributes
// (/build/angular-web-experiment), data-guide attributes (/build/angular-guides-surveys).
import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `<h1>Home</h1>`,
})
export class Home {}

// -- src/app/projects/new-project.ts --
// Keep lines other Angular pages added here: /build/angular-feature-flags replaces this
// file with a flag-driven version; if you followed it, keep that version.
import { Component, signal } from '@angular/core';
import * as amplitude from '@amplitude/unified';

@Component({
  selector: 'app-new-project',
  template: `
    <select #picker (change)="template.set(picker.value)">
      <option value="blank">Blank</option>
      <option value="kanban">Kanban</option>
    </select>
    <button type="button" (click)="create()">Create project</button>
  `,
})
export class NewProject {
  protected readonly template = signal('blank');

  protected create(): void {
    // ... your create logic

    amplitude.track('Project Created', { template: this.template() });
  }

// -- src/app/app.routes.ts --
// Keep lines other Angular pages added here: their routes, such as team and billing
// (/build/angular-session-replay), projects/:projectId and settings (/build/angular-heatmaps),
// and pricing, welcome, login, account, plans and offer/:code (/build/angular-web-analytics),
// and the title on each route if you added titles there.
import { Routes } from '@angular/router';
import { Home } from './home';
import { NewProject } from './projects/new-project';

export const routes: Routes = [
  { path: '', component: Home },
  { path: 'projects/new', component: NewProject },
];
```

## Hand it to your agent

Prefer to have Cursor, Claude Code, or Codex CLI do the wiring? This prompt points the agent at this page and pins the decisions that matter.

Using Cursor or Claude Code? Paste this prompt

```text
Add Amplitude analytics to this Angular app by following
https://amplitude.com/build/angular-product-analytics exactly. Install
@amplitude/unified and run ng generate environments. Put production and
amplitudeApiKey in both src/environments/environment.ts (production: true)
and environment.development.ts (production: false); leave the key as
your-project-api-key and tell me where to paste mine. Create
src/app/amplitude.ts exporting initAmplitude(), which calls initAll once
with autocapture elementInteractions on and sessionReplay.sampleRate set
to environment.production ? 0.01 : 1. Call initAmplitude() in
src/main.ts before bootstrapApplication, never in a component, service,
provideAppInitializer, app.config.ts, or main.server.ts. Don't subscribe
to Router events for page views; autocapture tracks navigations. If the
app has auth, expose the user as a signal (toSignal for observables) and
add amplitude.reset() to its sign-out method after sign-out succeeds; if
not, create the page's stand-in src/app/auth.service.ts. Create
src/app/amplitude-identity.service.ts as on the page: return early when
not in the browser (isPlatformBrowser), set the user ID with a "user-"
prefix and plan with Identify in an effect, and never call reset() when
the user is null. Use the root component file the app already has
(app.ts or app.component.ts); never create a second one. Inject the
service in its constructor and merge the nav, sign-in and sign-out
buttons into the existing root template. Add the page's Home and
NewProject components and merge both routes into the existing array in
src/app/app.routes.ts. Only call track from click handlers, and invent no
event names other than Project Created.

Other Amplitude Build pages for Angular add lines to these same files
(the environment files, amplitude.ts, auth.service.ts, the root
component, home.ts, new-project.ts, app.routes.ts). If any of these files
already exist, merge into them and never delete lines another Amplitude
Build page added, such as the experiment block, a setAmplitudeReady(...)
wrapper, debugMode and privacyConfig, or a webExperiment.plugin()
registration in amplitude.ts.

Tell me session replay records every session under ng serve and about 1%
in production, and how to turn it off. Report every file you changed.
```

## What this unlocks

- [Session replay](https://amplitude.com/session-replay), already on from step 2, lets you watch the session where `Project Created` fired with `template: kanban`.
- [Heatmaps](https://amplitude.com/heatmaps) show where people click on `/projects/new` before they create one, built from those same session replays.
- [Feature Experiment](https://amplitude.com/amplitude-experiment), bundled in the same package, can test a new template picker with `Project Created` as the metric.
- [Guides and surveys](https://amplitude.com/guides-and-surveys), also bundled, can prompt users who viewed `/projects/new` but never fired `Project Created`.

One integration; everything else reads from the same events.

## Next steps

[Web frameworksHow to add web analytics to an Angular appKeep campaign attribution through router redirects and read traffic in the web analytics dashboards.](/build/angular-web-analytics)[Web frameworksHow to add session replay to an Angular appTune the sample rate and mask sensitive components before you record real users.](/build/angular-session-replay)[Web frameworksHow to add feature flags and experiments to an Angular appRead a flag from a signal-based service and use \`Project Created\` as the experiment's metric.](/build/angular-feature-flags)

Reference docs

[Amplitude Quickstart →](https://amplitude.com/docs/get-started/amplitude-quickstart)[Browser Unified SDK →](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk)[Browser SDK 2 →](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2)[Configure session replay sampling →](https://amplitude.com/docs/session-replay/configure-sampling)[Debug analytics →](https://amplitude.com/docs/analytics/debug-analytics)[Amplitude MCP server →](https://amplitude.com/docs/amplitude-ai/amplitude-mcp)

Amplitude is the leading [all-in-one analytics platform](https://amplitude.com/) for building better products. Product and engineering teams use Amplitude to connect [product analytics](https://amplitude.com/amplitude-analytics) and [web analytics](https://amplitude.com/web-analytics) with [session replay](https://amplitude.com/session-replay), [heatmaps](https://amplitude.com/heatmaps), [experimentation](https://amplitude.com/amplitude-experiment), [feature flags](https://amplitude.com/feature-management), and [guides and surveys](https://amplitude.com/guides-and-surveys) in one unified workflow, with [AI agents](https://amplitude.com/ai-agents) working across all of it. Learn how Amplitude is building [self-improving products](https://amplitude.com/self-improving-products) with [Wave](https://amplitude.com/wave), helping teams turn product data into PRs.

For agents

[View raw .md](/build/angular-product-analytics.md)[llms.txt](/llms.txt)

## Ship the instrumentation, then read the data

Once events are flowing, Amplitude turns them into funnels, retention curves, and session replays without any further setup.

[Create a free account](https://app.amplitude.com/signup)[Browse all Build tutorials](https://amplitude.com/build)
