How to add a cookie consent banner to an Angular app
In an Angular CLI project, paste the snippet in the head of src/index.html, the page the build uses for every route, not in angular.json's scripts list or a component. Mark tracking tags in the same file, and read the visitor's choice in a small service that listens to Cookiequiet's cq:consent event; send route-change page views on NavigationEnd only when analytics is granted.Steps
Where does the snippet go in Angular?
The Angular CLI generates the application's HTML page from the file named by the index option in angular.json, by default src/index.html. The build injects its own scripts into that page, so a tag you write in its head comes before them:
html<!doctype html><html lang="en"><head><meta charset="utf-8"><title>MyApp</title><base href="/"><meta name="viewport" content="width=device-width, initial-scale=1"><script src="https://cookiequiet.com/cq.js" data-site="YOUR_SITE_KEY" data-consent-mode="1" data-auto-block="1" defer></script></head><body><app-root></app-root></body></html>
Why not the scripts option in angular.json? That option takes files from your project and builds each one into a separate bundle injected by the CLI. The snippet is a remote tag whose attributes carry its settings, so it belongs in index.html as written. Adding it from a component would also be too late: components run after the tags in the page.
How do I block my tags until consent?
Paste tracking tags under the snippet in src/index.html and change the opening tag:
html<script type="text/plain" data-cq-category="analytics" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script><script type="text/plain" data-cq-category="analytics">window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'G-XXXXXXX');</script>
Cookiequiet runs them when analytics is granted, and at once on later visits where the stored choice includes it. The Google Analytics 4 guide covers the Consent Mode alternative, and the guide on blocking scripts covers other tags. Scripts that a library creates at runtime are held by the auto-blocker when they come from one of the about 80 trackers it knows.
How does Angular code read the choice?
Cookiequiet dispatches a cq:consent event on window each time it applies a choice: a stored one on load, a new one after a click, and every category in regions where geo-targeting does not show the banner. event.detail.categories lists the granted categories; window.Cookiequiet.get() returns the choice in force (categories in c, a: "geo" where no banner is shown) or null while no choice applies yet. A service built on a signal:
ts// src/app/consent.service.tsimport { Injectable, signal } from "@angular/core";@Injectable({ providedIn: "root" })export class ConsentService {readonly granted = signal<string[]>((window as any).Cookiequiet?.get()?.c ?? []);constructor() {window.addEventListener("cq:consent", (e) =>this.granted.set((e as CustomEvent).detail.categories),);}has(category: string): boolean {return this.granted().includes(category);}openPreferences(): void {(window as any).Cookiequiet?.showPreferences();}}
In a template:
html@if (consent.has('marketing')) {<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID" title="Video"></iframe>} @else {<button (click)="consent.openPreferences()">Allow videos</button>}
With server-side rendering, window does not exist on the server: create the listener only in the browser, for example behind isPlatformBrowser.
What about route changes?
The Angular router changes views without reloading the page. The snippet runs once per full load, the choice stays applied and the banner does not reappear. Marked scripts are activated on load and after each choice, so keep them in index.html. For page views per route, listen for NavigationEnd:
tsconst consent = inject(ConsentService);inject(Router).events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd)).subscribe((e) => {if (consent.has("analytics")) sendPageView(e.urlAfterRedirects); // your analytics call});
How do I add the Cookie preferences link?
html<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>
Cookiequiet handles clicks on this link anywhere in the page. Keep it as a plain href, not a routerLink.
How do I test it?
- Run
ng buildand serve the output, orng serve, then view the page source:cq.jsis in the head before the CLI's scripts. - Open the app in a private window and click Reject all: the cookies are
cq_consentand your own. - Clear the cookies, click Accept all, reload: the marked tags run. Navigate and check the page views.
- Open the consent log in the dashboard.
A Content Security Policy must allow https://cookiequiet.com in script-src and connect-src. Cookiequiet is free for one website with geo-targeting; Starter is €3 per website per month, with unlimited pageviews.