# 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.

Source: https://cookiequiet.com/platforms/angular
Published: 2026-10-21
Publisher: Cookiequiet

## Steps

1. **Copy the snippet** In the Cookiequiet dashboard, add the website and copy the snippet with your site key, data-consent-mode="1" and data-auto-block="1".
2. **Paste it in src/index.html** Open src/index.html (the file named by the index option in angular.json) and paste the snippet in the head, before any other script.
3. **Mark your tags** Paste analytics and advertising tags below the snippet with type="text/plain" and data-cq-category set to analytics or marketing.
4. **Add a consent service** Create an injectable service that keeps the granted categories in a signal, starting from window.Cookiequiet.get() and updated on the cq:consent event.
5. **Gate embeds and page views** Wrap embeds in @if blocks that check the service, and send page views on the router's NavigationEnd event only when analytics is granted.
6. **Add the Cookie preferences link** In the footer component, add a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
7. **Build and test** Run 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.

## 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](/platforms/google-analytics-4) covers the Consent Mode alternative, and the [guide on blocking scripts](/guides/block-scripts-until-consent) 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
  });
```

## 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?

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

### Can I add Cookiequiet through the scripts array in angular.json?

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.

### Does the banner reappear when the Angular router navigates?

No. The router swaps views in the same document, so the script runs once per full page load and the stored choice stays applied.

### How do I read the choice in a component?

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.

### Does it work with Angular server-side rendering?

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.

## Sources

- [Angular: Workspace configuration (index and scripts options)](https://angular.dev/reference/configs/workspace-config)
- [Angular: Signals](https://angular.dev/guide/signals)
- [Angular: Router reference (router events)](https://angular.dev/guide/routing/router-reference)
