Angular中基于Cookie授权的Google Analytics启用/禁用异常问题
问题描述
我正尝试在Angular项目中集成Google Analytics与Cookie授权功能,需求为:用户授权后启动Google Analytics追踪,未授权则禁用,默认状态为禁用。
我使用window['ga-disable-${environment.gaTrackingId}'] = false;启用Google Analytics,window['ga-disable-${environment.gaTrackingId}'] = true;禁用它。当前代码可实现默认禁用Google Analytics,但用户点击授权(allow)后,Google Analytics无法启动。
原app.component.ts代码如下:
import { Component, HostListener, OnInit, OnDestroy } from '@angular/core'; import { NgcCookieConsentService, NgcInitializeEvent, NgcNoCookieLawEvent, NgcStatusChangeEvent } from 'ngx-cookieconsent'; import { Subscription } from 'rxjs'; import { environment } from 'src/environments/environment'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit, OnDestroy { isShow ? : boolean; topPosToStartShowing = 100; public consent!: string; notTrackScript!: any; trackScript!: any; //keep refs to subscriptions to be able to unsubscribe later private popupOpenSubscription!: Subscription; private popupCloseSubscription!: Subscription; private initializeSubscription!: Subscription; private statusChangeSubscription!: Subscription; private revokeChoiceSubscription!: Subscription; private noCookieLawSubscription!: Subscription; constructor(private ccService: NgcCookieConsentService, private title: Title) { if (environment.gaTrackingId) { // register google tag manager const gTagManagerScript = document.createElement('script'); gTagManagerScript.async = true; gTagManagerScript.src = `https://www.googletagmanager.com/gtag/js?id=${environment.gaTrackingId}`; document.head.appendChild(gTagManagerScript); // register google analytics const gaScript = document.createElement('script'); gaScript.innerHTML = ` window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', '${environment.gaTrackingId}'); `; document.head.appendChild(gaScript); this.notTrackScript = document.createElement('script'); this.notTrackScript.innerHTML = `window['ga-disable-${environment.gaTrackingId}'] = true;` document.head.appendChild(this.notTrackScript) this.trackScript = document.createElement('script'); this.trackScript.innerHTML = `window['ga-disable-${environment.gaTrackingId}'] = false;` } } ngOnInit(): void { this.statusChangeSubscription = this.ccService.statusChange$.subscribe( (event: NgcStatusChangeEvent) => { // you can use this.ccService.getConfig() to do stuff... console.log(`initialized: ${JSON.stringify(event)}`); if (event.status === 'allow') { document.head.removeChild(this.notTrackScript) document.head.appendChild(this.trackScript) location.reload() } }); } ngOnDestroy(): void { // unsubscribe to cookieconsent observables to prevent memory leaks this.popupOpenSubscription.unsubscribe(); this.popupCloseSubscription.unsubscribe(); this.initializeSubscription.unsubscribe(); this.statusChangeSubscription.unsubscribe(); this.revokeChoiceSubscription.unsubscribe(); this.noCookieLawSubscription.unsubscribe(); } }
问题分析
- 脚本执行顺序错误:GA初始化脚本先于禁用脚本执行,GA初始化时未读取到
ga-disable-*变量,导致默认禁用逻辑可能失效;用户授权后刷新页面,又重新执行初始流程,再次设置禁用状态,无法激活GA。 - 依赖页面刷新:
location.reload()会重置应用状态,无法持久化用户的授权选择,且GA需要在授权后主动重新初始化才能开始追踪。 - 未初始化的订阅变量:代码中声明的多个订阅变量(如
popupOpenSubscription)未赋值,直接在ngOnDestroy中销毁会抛出错误。
解决方案
修改后的代码优化了执行顺序,移除不必要的刷新,处理订阅变量,并持久化授权状态:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { NgcCookieConsentService, NgcStatusChangeEvent } from 'ngx-cookieconsent'; import { Subscription } from 'rxjs'; import { environment } from 'src/environments/environment'; import { Title } from '@angular/platform-browser'; declare var gtag: Function; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit, OnDestroy { isShow?: boolean; topPosToStartShowing = 100; private gaTrackingId = environment.gaTrackingId; // 初始化所有订阅变量为null private popupOpenSubscription?: Subscription; private popupCloseSubscription?: Subscription; private initializeSubscription?: Subscription; private statusChangeSubscription?: Subscription; private revokeChoiceSubscription?: Subscription; private noCookieLawSubscription?: Subscription; constructor(private ccService: NgcCookieConsentService, private title: Title) { if (this.gaTrackingId) { // 1. 先设置默认禁用GA window[`ga-disable-${this.gaTrackingId}`] = true; // 2. 加载GTAG脚本 const gTagManagerScript = document.createElement('script'); gTagManagerScript.async = true; gTagManagerScript.src = `https://www.googletagmanager.com/gtag/js?id=${this.gaTrackingId}`; document.head.appendChild(gTagManagerScript); // 3. 初始化GTAG基础逻辑 const gaScript = document.createElement('script'); gaScript.innerHTML = ` window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); `; document.head.appendChild(gaScript); } } ngOnInit(): void { // 检查用户已有的Cookie选择,初始化GA状态 const currentConsent = this.ccService.getStatus(); this.updateGaStatus(currentConsent.status); // 监听Cookie状态变化 this.statusChangeSubscription = this.ccService.statusChange$.subscribe( (event: NgcStatusChangeEvent) => { this.updateGaStatus(event.status); }); } private updateGaStatus(status: string): void { if (!this.gaTrackingId) return; if (status === 'allow') { // 启用GA并重新初始化 window[`ga-disable-${this.gaTrackingId}`] = false; gtag('config', this.gaTrackingId); } else { // 禁用GA window[`ga-disable-${this.gaTrackingId}`] = true; } } ngOnDestroy(): void { // 只销毁已初始化的订阅,避免报错 this.popupOpenSubscription?.unsubscribe(); this.popupCloseSubscription?.unsubscribe(); this.initializeSubscription?.unsubscribe(); this.statusChangeSubscription?.unsubscribe(); this.revokeChoiceSubscription?.unsubscribe(); this.noCookieLawSubscription?.unsubscribe(); } }
关键优化点
- 执行顺序调整:先设置
ga-disable-*为true,再加载GA脚本,确保GA初始化时处于禁用状态。 - 移除页面刷新:直接修改全局变量并调用
gtag('config', ...)重新初始化GA,无需刷新页面即可生效。 - 持久化状态处理:在
ngOnInit中检查用户已有的Cookie选择,初始化GA状态,确保刷新页面后保持用户的选择。 - 安全销毁订阅:使用可选链操作符
?.只销毁已赋值的订阅变量,避免空值报错。
内容的提问来源于stack exchange,提问作者Utkarsh Khare
相关产品推荐
相关产品推荐

