Skip to main content
Back to Guides
Compliance5 min read

Angular Cookie Consent: The 2026 Developer Guide

Angular bootstraps one JavaScript bundle and never reloads the page on a route change, so a tracker in index.html or the angular.json scripts array runs before your app decides anything. Here's where the consent loader belongs, how to block scripts, and how to expose consent through a service.

An Angular app is a single JavaScript bundle that boots once and swaps views without reloading the page. That architecture trips up cookie consent in two ways. Add Google Analytics to index.html and it fires before Angular even bootstraps. Register a tracking library in the angular.json scripts array and it's bundled straight into the app, running the moment the page loads. Neither waits for a visitor to agree, and because the router never reloads, a naive banner that checks consent "on page load" only ever checks once.

Regulators penalize exactly this. France's CNIL fined SHEIN 150 million euros in September 2025, in part because trackers fired before any choice and kept going after "Reject all" (CNIL). If your Angular app serves the EU, the UK, or a US opt-out state, you need real blocking plus consent state that flows through your services.

Where Angular loads global scripts

Two places put a script on every page. The first is src/index.html, the base document Angular loads the app into. The second is the scripts array in angular.json, under projects.[name].architect.build.options.scripts. Per the Angular docs, those scripts are "loaded exactly as if you had added them in a <script> tag inside index.html" (Angular workspace config). You can pass { input, inject: false, bundleName } to build a bundle without auto-injecting it, which matters when you want to load a tracker only after consent instead of on boot.

Where the consent loader goes

Put the loader in the <head> of index.html, as the first script, ahead of anything else Angular or your build injects. It has to run before the tags it governs.

<!-- src/index.html -->
<head>
  <meta charset="utf-8" />
  <base href="/" />
  <!-- FIRST, before the Angular bundle and any tracker -->
  <script async src="https://cdn.cookiebeam.com/banner/YOUR_BANNER_ID/default/loader.js"></script>
</head>

Replace YOUR_BANNER_ID with your banner's public ID from the dashboard. Keep trackers out of the angular.json scripts array unless they're wrapped in the blocking pattern below, since anything in that array bundles into the app and runs on load.

Block trackers until consent

Load trackers disabled and switch them on after opt-in. CookieBeam activates any script tagged type="text/plain" with a data-category once the visitor accepts that category:

<!-- src/index.html, after the loader -->
<script
  type="text/plain"
  data-category="analytics"
  data-cookiebeam-managed="true"
  src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX">
</script>

The browser skips a text/plain script until the consent runtime enables it, so a rejected visit loads nothing. For the underlying technique, see how to block scripts before consent.

Set Consent Mode defaults with an initializer

Consent Mode v2 needs the defaults set to denied before any Google tag runs. In Angular you can guarantee that ordering with an app initializer, which runs during bootstrap before the first component renders. On modern standalone apps use provideAppInitializer; on older module apps use the APP_INITIALIZER token.

// app.config.ts (standalone, Angular 19+)
import { ApplicationConfig, provideAppInitializer } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideAppInitializer(() => {
      const w = window as any;
      w.dataLayer = w.dataLayer || [];
      function gtag(){ w.dataLayer.push(arguments); }
      gtag('consent', 'default', {
        ad_storage: 'denied',
        analytics_storage: 'denied',
        ad_user_data: 'denied',
        ad_personalization: 'denied',
        wait_for_update: 500
      });
    })
  ]
};

CookieBeam sets these defaults and pushes the update on acceptance out of the box, so this is only needed if you manage Consent Mode yourself. The advanced versus basic Consent Mode guide explains which mode to pick.

Expose consent through a service

Angular components should read consent from an injectable service, not by poking at cookies directly. Subscribe to the cookiebeam:consent event, and because that event fires outside Angular's zone, re-enter the zone with NgZone.run so change detection actually updates the view:

// consent.service.ts
import { Injectable, NgZone } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

type Consent = { analytics: boolean; marketing: boolean; preferences: boolean };

@Injectable({ providedIn: 'root' })
export class ConsentService {
  private state = new BehaviorSubject<Consent>({ analytics: false, marketing: false, preferences: false });
  readonly consent$ = this.state.asObservable();

  constructor(private zone: NgZone) {
    window.addEventListener('cookiebeam:consent', (e: any) => {
      this.zone.run(() => this.state.next(e.detail));
    });
  }
}

Any component or guard can inject ConsentService, subscribe to consent$, and lazy-load a feature that carries analytics only when the relevant category is granted. The NgZone.run wrapper is the detail people miss: without it, a browser event updates the subject but the template never re-renders.

Client-side routing and single-page timing

Because Angular's router swaps views without a full reload, a banner that only initializes on the first load is enough, as long as consent state is event-driven rather than checked once. Gate lazy-loaded routes that pull in tracking with a guard that reads the consent service, and confirm your banner reappears when a visitor opens preferences from any route, not the homepage alone. The single-page app consent guide covers route-change handling that applies to Angular, React, and Vue alike.

Policy and testing

Publish a cookie policy route and link it from the banner and footer. Every new SDK you bundle can add cookies, so a scanner that keeps the policy table current saves you from a stale hand-written list (scanning versus manual audits). Then test the production build:

  • Open a fresh private window from an EU IP and confirm only necessary cookies exist before you interact.
  • Reject, and verify no _ga or _fbp appears and no beacons fire in the Network tab.
  • Accept, and confirm the tags load and Consent Mode reports granted.
  • Move between routes and reopen preferences to confirm state stays consistent.

CookieBeam blocks unknown scripts by default and keeps a timestamped consent record, so an Angular app clears the same audit a dedicated tool would. Run a full consent audit after any new dependency, and mind the banner performance guide since the loader sits in the critical path.

Angular Cookie Consent: 2026 Developer Guide | CookieBeam | CookieBeam