Skip to content
PolicyStack
V1
Esc
navigateopen⌘Jpreview
On this page

Add cookie consent to Angular

Angular 20+ adapter — providePolicyStackConsent, ConsentService, *ocConsent

PolicyStack V1 — current documentation. Supported capabilities and limitations.

Angular 20+ adapter for Consent. Bridges @policystack/core/consent with Angular’s signal reactivity.

Install

pnpm add @policystack/core @policystack/angular

Peer dependencies: @angular/core >= 18, @angular/common >= 18.

Setup

Register the provider once at the root of your standalone application:

import { bootstrapApplication } from "@angular/platform-browser";
import { providePolicyStackConsent } from "@policystack/angular/consent";
import { localStorageAdapter } from "@policystack/core/consent/storage/local-storage";
import { AppComponent } from "./app.component";

bootstrapApplication(AppComponent, {
	providers: [
		providePolicyStackConsent({
			config: {
				categories: [
					{ key: "essential", label: "Essential", locked: true },
					{ key: "analytics", label: "Analytics" },
					{ key: "marketing", label: "Marketing" },
				],
				adapter: localStorageAdapter(),
			},
		}),
	],
});

You can pass a pre-created store instead of config:

import { createConsentStore } from "@policystack/core/consent";

const store = createConsentStore({ categories });
providePolicyStackConsent({ store });

API

ConsentService

Inject ConsentService anywhere to read consent state via signals and trigger actions.

import { Component, inject } from "@angular/core";
import { ConsentService } from "@policystack/angular/consent";

@Component({
	selector: "app-banner",
	standalone: true,
	template: `
		@if (consent.route() === "cookie") {
			<div>
				<button (click)="consent.acceptNecessary()">Necessary only</button>
				<button (click)="consent.acceptAll()">Accept all</button>
				<button (click)="consent.setRoute('preferences')">Customize</button>
			</div>
		}
	`,
})
export class BannerComponent {
	readonly consent = inject(ConsentService);
}

Signal properties: route, categories, decisions, draft, jurisdiction, policyVersion, decidedAt, repromptReason, state.

Methods: acceptAll, acceptNecessary, reject, toggle, save, setRoute, has, getConsentRecord, getPreviousRecord.

injectCategory(key)

Granular per-category access. Must be called inside an injection context (e.g. a component constructor or field initializer).

toggle stages the change and granted() reflects it instantly (it reads the pending state.draft), but nothing is applied — has(), <ConsentGate>, script gating, and storage only change when save() promotes the draft. Leaving the preferences route without saving discards it.

import { Component } from "@angular/core";
import { injectCategory } from "@policystack/angular/consent";

@Component({
	selector: "category-row",
	standalone: true,
	template: `
		<label>
			<input type="checkbox" [checked]="analytics.granted()" (change)="analytics.toggle()" />
			Analytics
		</label>
	`,
})
export class CategoryRowComponent {
	readonly analytics = injectCategory("analytics");
}

*ocConsent directive

Structural directive that conditionally renders content based on a consent expression. Mirrors @policystack/react/consent’s <ConsentGate> and @policystack/vue/consent’s <ConsentGate>.

import { Component } from "@angular/core";
import { ConsentGate } from "@policystack/angular/consent";
import { ChartComponent } from "./chart.component";
import { EnablePromptComponent } from "./enable-prompt.component";

@Component({
	selector: "gated-chart",
	standalone: true,
	imports: [ConsentGate, ChartComponent, EnablePromptComponent],
	template: `
		<chart *ocConsent="'analytics'; else fallback"></chart>
		<ng-template #fallback>
			<enable-prompt></enable-prompt>
		</ng-template>

		<personalized-promo *ocConsent="{ and: ['analytics', 'marketing'] }"></personalized-promo>
	`,
})
export class GatedChartComponent {}

The directive emits no DOM wrapper — only the templated content (or its fallback) is rendered.

SSR (Angular Universal)

providePolicyStackConsent runs the store factory once per request injector on the server, which is the right scope for cookie/header-based jurisdiction or storage. Use @policystack/core/consent/storage/server for a server-side adapter seeded from the inbound request, and the local-storage / cookie adapters in the browser. See @policystack/core/consent for storage adapter details.

Shared concepts

Categories, GPC handling, jurisdiction resolvers, re-consent triggers, script gating (gateScript), and storage adapters all live in @policystack/core/consent — the Angular adapter is a thin reactivity wrapper.

See also

License

Apache-2.0

Last updated on September 6, 2026