Platforms

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.
Published 21 October 2026 · By the Cookiequiet team · Markdown

Steps

Copy the snippetIn the Cookiequiet dashboard, add the website and copy the snippet with your site key, data-consent-mode="1" and data-auto-block="1".
Paste it in src/index.htmlOpen src/index.html (the file named by the index option in angular.json) and paste the snippet in the head, before any other script.
Mark your tagsPaste analytics and advertising tags below the snippet with type="text/plain" and data-cq-category set to analytics or marketing.
Add a consent serviceCreate an injectable service that keeps the granted categories in a signal, starting from window.Cookiequiet.get() and updated on the cq:consent event.
Gate embeds and page viewsWrap embeds in @if blocks that check the service, and send page views on the router's NavigationEnd event only when analytics is granted.
Add the Cookie preferences linkIn the footer component, add a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
Build and testRun ng build or ng serve, open the app in a private window, click Reject all and check that no _ga or _fbp cookie is set. Accept all, reload and check the consent log.

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.

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.ts
import { 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:

ts
const 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
});
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?

  1. Run ng build and serve the output, or ng serve, then view the page source: cq.js is in the head before the CLI's scripts.
  2. Open the app in a private window and click Reject all: the cookies are cq_consent and your own.
  3. Clear the cookies, click Accept all, reload: the marked tags run. Navigate and check the page views.
  4. 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.

Questions

No. That option bundles files from your project at build time. The snippet is a remote script whose data attributes carry its settings, and it must come before your other tags, so paste it in src/index.html.
No. The router swaps views in the same document, so the script runs once per full page load and the stored choice stays applied.
Inject a service that keeps the granted categories in a signal, starting from window.Cookiequiet.get() and updated on the cq:consent event, and check it in the template with an @if block.
Yes. The snippet in index.html is served with the rendered page and runs in the browser. Only code that touches window, such as the consent service listener, must run in the browser alone.

Put a quiet banner on your website today

Free for one website with every core feature, no card and no trial clock. Upgrade a website to Starter for $3 a month to match your brand with AI and drop the branding, or to Pro for $9 to design it with the AI agent.