@studiohyperdrive/ngx-cookies
Version:
An integrated Angular approach to the vanilla cookieconsent library.
396 lines (388 loc) • 15.8 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, Directive, Optional, Inject, Input } from '@angular/core';
import * as CookieConsent from 'vanilla-cookieconsent';
import { Subject, BehaviorSubject, combineLatest, startWith, map, distinctUntilChanged, tap, takeUntil } from 'rxjs';
import * as i1 from '@studiohyperdrive/ngx-core';
import { flatten } from 'lodash';
/**
* A service that implements the CookieConsent from https://cookieconsent.orestbida.com and provides the necessary Observables to handle this setup.
*/
class NgxCookieService {
constructor(windowsService) {
this.windowsService = windowsService;
/**
* Subject to hold the onFirstConsent event
*/
this.firstCookiesConsentedSubject = new Subject();
/**
* Subject to hold the onConsent event
*/
this.cookiesConsentedSubject = new Subject();
/**
* Subject to hold the consentChanged event
*/
this.cookiesConsentChangedSubject = new Subject();
/**
* Subject to hold the modalVisible event
*/
this.modalVisibleSubject = new BehaviorSubject(false);
/**
* Subject to hold the cookiesChanged event
*/
this.cookiesChangedSubject = new Subject();
/**
* An event triggered only the very first time that the user expresses their choice of consent
*/
this.firstCookiesConsented$ = this.firstCookiesConsentedSubject.asObservable();
/**
* An event triggering the choice of consent on every page load.
*/
this.cookiesConsented$ = this.cookiesConsentedSubject.asObservable();
/**
* An event triggered when the user modifies their preferences and only if consent has already been provided.
*/
this.cookiesConsentChanged$ = this.cookiesConsentChangedSubject.asObservable();
/**
* Whether the cookies modal is currently visible
*/
this.modalVisible$ = this.modalVisibleSubject.asObservable();
/**
* Emits every time the set cookies have been changed
*/
this.cookiesChanged$ = this.cookiesChangedSubject.asObservable();
}
/**
* Sets up the CookieConsent.
*
* For more information of on this configuration, check the documentation
* https://cookieconsent.orestbida.com/essential/getting-started.html
*
* @param categories - The categories of cookies
* @param language - The language settings of the cookies
* @param configuration - An optional set configuration settings
*/
setupCookiesHandler(categories, language, configuration) {
// Iben: If we're not in the browser, we early exit, so server-side rendering can be enabled
if (!this.windowsService.isBrowser()) {
return;
}
// Iben: If no categories were provided, we early exit and throw an error, as the cookie preference cannot be shown without.
if (!categories || Object.keys(categories).length === 0) {
console.error('NgxCookies: No categories were provided for the cookie handler. At least one category is required.');
return;
}
// Iben: Setup the onFirstConsent listener
const onFirstConsent = (event) => {
this.firstCookiesConsentedSubject.next(event);
};
onFirstConsent.bind(this);
// Iben: Setup the onConsent listener
const onConsent = (event) => {
this.cookiesConsentedSubject.next(event);
};
onConsent.bind(this);
// Iben: Setup the onChange listener
const onChange = (event) => {
this.cookiesConsentChangedSubject.next(event);
};
onChange.bind(this);
// Iben: Setup the onModalShow listener
const onModalShow = () => {
this.modalVisibleSubject.next(true);
};
onModalShow.bind(this);
// Iben: Setup the onModalHide listener
const onModalHide = () => {
this.modalVisibleSubject.next(false);
};
onModalHide.bind(this);
// Iben: Run the CookieConsent handler
CookieConsent.run({
...(configuration || {}),
onFirstConsent,
onConsent,
onChange,
onModalHide,
onModalShow,
categories,
language,
});
}
/**
* Whether a certain category of cookies was accepted
*
* @param name - The name of the category
*/
hasAcceptedCategory(name) {
// Iben: Return every time the cookie consent has changed
return combineLatest([
this.cookiesConsented$.pipe(startWith(undefined)),
this.cookiesConsentChanged$.pipe(startWith(undefined)),
]).pipe(map(() => {
// Iben: Check if the category was accepted
return CookieConsent.acceptedCategory(name);
}));
}
/**
* Accept a category of cookies
*
* @param name - The name of the categories
*/
acceptCategory(name) {
CookieConsent.acceptCategory(name);
}
/**
* Whether a certain service within a category was accepted
*
* @param category - The name of the category
* @param name - The name of the service
*/
hasAcceptedService(category, name) {
// Iben: Return every time the cookie consent has changed
return combineLatest([
this.cookiesConsented$.pipe(startWith(undefined)),
this.cookiesConsentChanged$.pipe(startWith(undefined)),
]).pipe(map(() => {
// Iben: Check if the service within the provided category was accepted
return CookieConsent.acceptedService(name, category);
}));
}
/**
* Accept a category of cookies
*
* @param category - The name of the category
* @param name - The name of the service
*/
acceptService(category, name) {
CookieConsent.acceptService(name, category);
}
/**
* Show the cookie modal
*/
showModal() {
CookieConsent.show(true);
}
/**
* Get a cookie
*
* @param cookie - Name of the cookie
*/
getCookie(cookie) {
return (CookieConsent.getCookie('data') || {})[cookie];
}
/**
* Get a cookie as an observable
*
* @param cookie - Name of the cookie
*/
getCookieObservable(cookie) {
// Iben: Return every time the set cookies are changed
return this.cookiesChanged$.pipe(startWith(CookieConsent.getCookie('data') || {}),
// Iben: Get the cookie value
map(() => {
return this.getCookie(cookie);
}),
// Iben: As the cookiesChanged event emits every time all cookies are rest, we check if the cookie value was really changed
distinctUntilChanged());
}
/**
* Set a cookie
*
* @param cookie - The cookie we wish to set
*/
setCookie(cookie) {
// Iben: Set the cookie
const isSet = CookieConsent.setCookieData({
value: { [cookie.name]: cookie.value },
mode: 'update',
});
// Iben: If the cookie was not set, we return
if (!isSet) {
return;
}
// Iben: Update the subject so we can notify listeners
this.cookiesChangedSubject.next(CookieConsent.getCookie('data'));
}
/**
* Remove a cookie
*
* @param cookie - The cookie we wish to remove
*/
removeCookie(cookie) {
// Iben: Get the current cookies
const currentCookies = CookieConsent.getCookie('data') || {};
// Iben: Remove the cookie from the currently set cookies
const { [cookie]: _removedValue, ...value } = currentCookies;
// Iben: Remove the cookie from the cookies holder
const isSet = CookieConsent.setCookieData({ value, mode: 'overwrite' });
// Iben: If the cookie was not set, we return
if (!isSet) {
return;
}
// Iben: Update the subject so we can notify listeners
this.cookiesChangedSubject.next(CookieConsent.getCookie('data'));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxCookieService, deps: [{ token: i1.NgxWindowService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxCookieService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxCookieService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.NgxWindowService }] });
/** A token to provide a fallback component that needs to be rendered when a specific (set of) cookie(s) was not accepted. */
const NgxCookiesFallbackComponentToken = new InjectionToken('NgxCookiesFallbackComponent');
/**
* A structural directive that provides a way to render UI elements based on whether a (set of) cookie(s) have been accepted by the user.
*/
class NgxHasCookieDirective {
/**
* A cookie or list of cookies the item should have
*/
set hasCookie(cookie) {
this.cookies = Array.isArray(cookie) ? cookie : [cookie];
this.updateView();
}
/**
* The else template in case the cookie is not accepted
*/
set hasCookieElse(ngTemplate) {
this.elseTemplateRef = ngTemplate;
this.updateView();
}
constructor(templateRef, viewContainer, ngxCookieService, cdRef, component) {
this.viewContainer = viewContainer;
this.ngxCookieService = ngxCookieService;
this.cdRef = cdRef;
this.component = component;
/**
* The needed templateRefs
*/
this.thenTemplateRef = null;
this.elseTemplateRef = null;
/**
* The list of cookies we need to check
*/
this.cookies = [];
this.thenTemplateRef = templateRef;
}
ngOnDestroy() {
this.dispose();
}
/**
* Updates the view and hides/renders the template as needed
*/
updateView() {
// Iben: Dispose the current subscription
this.dispose();
// Iben: Create a new onDestroyed handler
this.destroyed$ = new Subject();
// Iben: Render the views based on the correct state
combineLatest(
// Iben Check for each cookie if it is accepted
this.cookies.map((cookie) => {
// Iben: If no specific services were provided, we can just check the category
if (!cookie.services) {
return this.ngxCookieService.hasAcceptedCategory(cookie.category);
}
// Iben: If specific services were provided, we can just check each individual category
return combineLatest(cookie.services.map((service) => {
return this.ngxCookieService.hasAcceptedService(cookie.category, service);
}));
}))
.pipe(map((hasCookies) => {
return flatten(hasCookies).every((hasCookie) => hasCookie);
}), tap((hasCookie) => {
// Iben: Clear the current view
this.viewContainer.clear();
// Iben: If there already is a component, destroy it so it can update correctly
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = undefined;
}
// Iben: Render the correct templates
hasCookie ? this.renderThenTemplate() : this.renderElseTemplate();
// Iben: Detect the changes so that the view gets updated
this.cdRef.detectChanges();
}), takeUntil(this.destroyed$))
.subscribe();
}
/**
* Dispose the current subscription
*/
dispose() {
if (this.destroyed$) {
this.destroyed$.next();
this.destroyed$.complete();
}
}
/**
* Render the template on which the directive is set
*/
renderThenTemplate() {
// Iben: If a thenTemplateRef is provided, render the template
if (this.thenTemplateRef) {
this.viewContainer.createEmbeddedView(this.thenTemplateRef);
}
}
/**
* Render the fallbackTemplate or fallBackComponent
*/
renderElseTemplate() {
// Iben: If a custom template ref was provided, render the template and early exit
if (this.elseTemplateRef) {
this.viewContainer.createEmbeddedView(this.elseTemplateRef);
return;
}
// Iben: If a component was provided as a fallback, we render that
if (this.component) {
// Iben: Render the provided component
this.componentRef = this.viewContainer.createComponent(this.component);
// Iben: Set the cookies of the component
this.componentRef.setInput('cookies', this.cookies);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxHasCookieDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: NgxCookieService }, { token: i0.ChangeDetectorRef }, { token: NgxCookiesFallbackComponentToken, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.3", type: NgxHasCookieDirective, isStandalone: true, selector: "[hasCookie]", inputs: { hasCookie: "hasCookie", hasCookieElse: "hasCookieElse" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxHasCookieDirective, decorators: [{
type: Directive,
args: [{
selector: '[hasCookie]',
standalone: true,
}]
}], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: NgxCookieService }, { type: i0.ChangeDetectorRef }, { type: i0.Type, decorators: [{
type: Optional
}, {
type: Inject,
args: [NgxCookiesFallbackComponentToken]
}] }], propDecorators: { hasCookie: [{
type: Input
}], hasCookieElse: [{
type: Input
}] } });
/**
*An abstract component that can optionally be used as a fallback to content that depends on accepted cookies
*/
class NgxCookiesFallBackComponent {
constructor() {
/**
* The cookies that were required to show the original content
*/
this.cookies = [];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxCookiesFallBackComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.3", type: NgxCookiesFallBackComponent, isStandalone: true, inputs: { cookies: "cookies" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxCookiesFallBackComponent, decorators: [{
type: Directive
}], propDecorators: { cookies: [{
type: Input
}] } });
/*
* Public API Surface of ngx-cookies
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxCookieService, NgxCookiesFallBackComponent, NgxCookiesFallbackComponentToken, NgxHasCookieDirective };
//# sourceMappingURL=studiohyperdrive-ngx-cookies.mjs.map