Show the Numbers: GA4 + BigQuery for Angular 20+ to Prove UX/Performance Wins in Interviews

Show the Numbers: GA4 + BigQuery for Angular 20+ to Prove UX/Performance Wins in Interviews

Turn Core Web Vitals and UX events into interview‑ready charts with GA4 + BigQuery—code, queries, and CI checks you can copy into your Angular 20+ app today.

“We cut p75 LCP by 38% after the Signals refactor—here’s the BigQuery chart by route.”
Back to all posts

Interview Hook: Why Numbers Win

A real moment from the trenches

I’ve been in the interview where a director says, “Show me the numbers.” On a telecom advertising analytics dashboard we’d just optimized, I pulled a Looker Studio chart from BigQuery: p75 LCP dropped 38% after our Angular 20 + Signals refactor, and INP stabilized under 200 ms on mobile. The conversation flipped from hypotheticals to impact.

Why this matters for Angular 20+ teams

  • RUM complements Lighthouse CI—measure what real users experience by route, device, and release.

  • GA4 + BigQuery gives you a durable, low‑friction pipeline you can stand up in days.

  • Interviewers trust charts backed by SQL more than subjective claims.

Stack fit

  • Angular 20+, Signals/SignalStore for state.

  • Firebase/GA4 for analytics and consent mode v2.

  • BigQuery for SQL and Looker Studio or PrimeNG charts for presentation.

  • Nx monorepo for structure; GitHub Actions/Jenkins/Azure DevOps for CI.

// analytics.service.ts (Angular 20)
import { Injectable, inject } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { effect, signal } from '@angular/core';

declare global {
  interface Window { dataLayer: any[]; gtag: (...args: any[]) => void; }
}

@Injectable({ providedIn: 'root' })
export class AnalyticsService {
  private router = inject(Router);
  consent = signal<{ ad_storage: 'granted'|'denied'; analytics_storage: 'granted'|'denied' }>({
    ad_storage: 'denied', analytics_storage: 'denied'
  });
  releaseTag = signal('2025.01.0');
  role = signal<'admin'|'analyst'|'operator'|'guest'>('guest');
  tenantHash = signal<string>('anon'); // hash on server to avoid PII

  init(measurementId: string) {
    window.dataLayer = window.dataLayer || [];
    window.gtag = function(){ window.dataLayer.push(arguments); } as any;

    window.gtag('consent', 'default', {
      ad_storage: this.consent().ad_storage,
      analytics_storage: this.consent().analytics_storage,
    });
    window.gtag('js', new Date());
    window.gtag('config', measurementId, { send_page_view: false });

    // SPA page_view on route changes
    this.router.events.subscribe(evt => {
      if (evt instanceof NavigationEnd && this.consent().analytics_storage === 'granted') {
        window.gtag('event', 'page_view', {
          page_location: location.href,
          page_path: evt.urlAfterRedirects,
          route_path: evt.urlAfterRedirects,
          release_tag: this.releaseTag(),
          role: this.role(),
          tenant_id: this.tenantHash()
        });
      }
    });

    // react to consent changes
    effect(() => {
      const c = this.consent();
      window.gtag('consent', 'update', c);
    });
  }

  setConsent(analyticsGranted: boolean) {
    this.consent.set({
      ad_storage: 'denied',
      analytics_storage: analyticsGranted ? 'granted' : 'denied'
    });
  }
}

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { AnalyticsService } from './analytics.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    {
      provide: 'APP_INIT',
      multi: true,
      useFactory: (svc: AnalyticsService) => () => svc.init('G-XXXXXXX'),
      deps: [AnalyticsService]
    }
  ]
};

Install and configure GA4 in Angular

Use gtag.js directly or AngularFire Analytics. I prefer explicit gtag for fine control in regulated environments. Add environment flags for measurementId and region.

  • Honor consent before sending anything.

  • Default to denied, update on user action.

  • Use region hints when needed.

SPA page_view events

  • GA4 doesn’t auto‑detect Angular route changes; emit manually.

  • Include route_path, release_tag, and role/tenant (non‑PII) as parameters.

Send Core Web Vitals (LCP, INP, CLS) to GA4

npm i web-vitals

// vitals.service.ts
import { Injectable, inject } from '@angular/core';
import { onLCP, onINP, onCLS, Metric } from 'web-vitals';
import { AnalyticsService } from './analytics.service';

function sendVital(name: string, metric: Metric, params: Record<string, any>) {
  if (!('gtag' in window)) return;
  const value = name === 'CLS' ? Math.round(metric.value * 1000) : Math.round(metric.value); // ms
  window.gtag('event', name.toLowerCase(), {
    value,
    metric_id: metric.id,
    metric_value: value,
    metric_delta: Math.round('delta' in metric ? (metric as any).delta : value),
    route_path: params['route_path'],
    release_tag: params['release_tag'],
    role: params['role'],
    tenant_id: params['tenant_id']
  });
}

@Injectable({ providedIn: 'root' })
export class VitalsService {
  private analytics = inject(AnalyticsService);
  init() {
    const common = () => ({
      route_path: location.pathname,
      release_tag: this.analytics.releaseTag(),
      role: this.analytics.role(),
      tenant_id: this.analytics.tenantHash()
    });
    onLCP((m) => sendVital('LCP', m, common()));
    onINP((m) => sendVital('INP', m, common()));
    onCLS((m) => sendVital('CLS', m, common()));
  }
}

In GA4, create custom metrics (metric_value as Number) and custom dimensions (route_path, release_tag, role, tenant_id). This unlocks BigQuery grouping and p75 reporting by route and release. If you’re using Firebase, map Firebase Analytics events to the same names for parity across web/native.

Why web-vitals from real users

  • Lighthouse ≠ production. RUM captures device variance, 3G/4G, CPU contention, and server/SSR effects.

Emit metrics with event parameters

  • Use the web-vitals library.

  • Prefix custom params and register custom definitions in GA4.

  • Include route_path and release_tag for comparisons.

BigQuery Queries You Can Show in an Interview

-- p50/p75 LCP by route and release
WITH lcp AS (
  SELECT
    PARSE_DATE('%Y%m%d', event_date) AS date,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'route_path') AS route_path,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'release_tag') AS release_tag,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'metric_value') AS lcp_ms
  FROM `myproject.analytics_XXXXXX.events_*`
  WHERE event_name = 'lcp' -- lowercased as emitted
)
SELECT
  route_path,
  release_tag,
  APPROX_QUANTILES(lcp_ms, 100)[OFFSET(50)] AS p50_lcp,
  APPROX_QUANTILES(lcp_ms, 100)[OFFSET(75)] AS p75_lcp,
  COUNT(*) AS samples
FROM lcp
WHERE lcp_ms IS NOT NULL
GROUP BY route_path, release_tag
ORDER BY route_path, release_tag;

-- INP p75 trend by day and route
WITH inp AS (
  SELECT
    PARSE_DATE('%Y%m%d', event_date) AS date,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'route_path') AS route_path,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'metric_value') AS inp_ms
  FROM `myproject.analytics_XXXXXX.events_*`
  WHERE event_name = 'inp'
)
SELECT date, route_path,
  APPROX_QUANTILES(inp_ms, 100)[OFFSET(75)] AS p75_inp,
  COUNT(*) AS samples
FROM inp
GROUP BY date, route_path
ORDER BY date, route_path;

These queries drop straight into Looker Studio or a small Node.js service behind your Angular dashboard. I’ve also embedded them into automated checks so CI fails if p75 regresses by >10%. See below.

Median and p75 by route and release

Daily export is free; streaming export may require GA4 360—verify your plan. The GA4 export tables follow events_YYYYMMDD. Here’s a query I’ve used with a telecom dashboard to prove a 38% p75 LCP drop after a Signals refactor.

Compare before vs after

  • Tag releases in the client (release_tag) and guard your rollout with feature flags if needed.

CI Guardrails: Fail the Build on Regressions

# .github/workflows/rum-guard.yml
name: RUM Guard
on:
  workflow_dispatch:
  schedule:
    - cron: '0 10 * * *'
jobs:
  check-rum:
    runs-on: ubuntu-latest
    steps:
      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@v2
        with:
          project_id: ${{ secrets.GCP_PROJECT }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
      - name: Query p75 LCP by route
        run: |
          SQL="""
          WITH lcp AS (
            SELECT
              (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'route_path') AS route,
              (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'release_tag') AS rel,
              (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'metric_value') AS lcp
            FROM `${{ secrets.BQ_DATASET }}.events_*`
            WHERE event_name='lcp' AND _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
          )
          SELECT route, rel, APPROX_QUANTILES(lcp,100)[OFFSET(75)] AS p75 FROM lcp GROUP BY route, rel;"""
          echo "$SQL" > q.sql
          bq --quiet query --use_legacy_sql=false < q.sql > out.txt
          echo "Results:"; cat out.txt
          # Simple guard: fail if homepage exceeds 2500ms
          THRESH=$(awk '/home/{print $3}' out.txt | head -1)
          if [ -n "$THRESH" ] && [ "$THRESH" -gt 2500 ]; then
            echo "p75 LCP regression on /home: $THRESH ms"; exit 1;
          fi

Automate the interview slide

  • Synthetic + RUM: keep Lighthouse CI for lab checks and use BigQuery for real‑user gates.

  • Gate by route: fail only the affected pages.

GitHub Actions example

  • Service account JSON stored as secret.

  • bq CLI or gcloud to run queries and parse thresholds.

Privacy, Modeling, and Segmentation Without PII

For enterprise apps (multi‑tenant RBAC, regulated spaces), I’ve shipped this model in insurance telematics dashboards and device management portals. It keeps interview‑ready segmentation (admin vs operator, tenant A vs tenant B) without risking PII. If you need an Angular consultant to implement this with your compliance team, I’m available for hire.

  • Block until analytics_storage is granted.

  • Use region‑specific defaults where required.

Segment by role/tenant safely

  • Hash tenant IDs server‑side.

  • Only send role names, not emails or GUIDs.

  • Keep props within GA4 custom definitions limits.

Release tagging

  • Emit release_tag to compare before/after.

  • Feature flags via Firebase Remote Config optional.

Portfolio-Ready Dashboards

I keep a private interview pack with before/after charts from several projects:

  • Telecom analytics: p75 LCP 2.6s → 1.6s after route‑level code‑splitting and image CDN.
  • Insurance telematics: INP held under 180 ms across real‑time map screens after Signals + SignalStore refactor and event coalescing.
  • Broadcast scheduling: p75 navigation timing stabilized during heavy grid virtualization work.

If you want UI inspiration, browse my NG Wave component library—animated Angular components built with Signals—and adapt charts for your portfolio.

Looker Studio fast path

  • Connect BigQuery > GA4 export dataset.

  • Add scorecards for p50/p75 LCP/INP, tables by route, and a time series by release_tag.

  • Export as PDF for interviews.

Angular dashboard path

  • Serverless function that queries BigQuery.

  • PrimeNG/Highcharts visualizations with Signals for fast interactivity.

  • Host on CloudFront/S3, Azure Static Web Apps, or Firebase Hosting.

Show your craft, not just numbers

  • Tie charts to specific refactors: Signals adoption, data virtualization, WebSocket batching, design tokens.

  • Add annotations for rollouts and incident dates.

When to Hire an Angular Developer to Instrument GA4 + BigQuery

Need a remote Angular expert? I can instrument, wire queries, and hand you a polished dashboard—plus a README that turns into an interview deck. If you need to stabilize your codebase while we measure, I also offer modernization and rescue work.

You’ll save time if

  • You’re under interview pressure and need numbers this week.

  • Your app uses SSR/Signals and you need deterministic metrics.

  • You operate in regulated environments (HIPAA/SOC 2) and need guardrails.

What I bring

  • 10+ years across telecom, aviation, media, insurance.

  • Angular 20, Signals/SignalStore, Firebase, Nx, PrimeNG, Highcharts/D3.

  • CI/CD on GitHub Actions, Jenkins, Azure DevOps; AWS/Azure/GCP hosting.

  • Real examples: employee trackers, airport kiosks, ads analytics, telematics dashboards.

Implementation Notes: SSR, Signals, and Hydration

// example SignalStore for analytics context
import { signalStore, withState, withMethods } from '@ngrx/signals';

export const AnalyticsStore = signalStore(
  withState({ releaseTag: '2025.01.0', role: 'guest' as const, tenantHash: 'anon' }),
  withMethods((store) => ({
    setRelease(tag: string) { store.releaseTag = tag; },
    setRole(role: 'guest'|'admin'|'analyst'|'operator') { store.role = role; },
    setTenant(hash: string) { store.tenantHash = hash; }
  }))
);

SSR considerations

  • Guard window/gtag access behind isPlatformBrowser.

  • Defer vitals init until after hydration for accuracy.

Signals/SignalStore fit

  • Store release_tag, role, and consent as signals for zero‑jank updates.

  • Expose a SignalStore to share telemetry context across lazy routes.

Routing granularity

  • Normalize route_path (strip IDs) to avoid cardinality explosions in GA4/BigQuery.

  • Batch noisy events; don’t spam GA4 with high‑frequency telemetry.

How an Angular Consultant Proves UX Wins With Real-User Metrics

Timeline can flex. If you need deeper performance work—SSR, image/CDN, bundle splits, WebSocket batching—I pair the instrumentation with fixes to move the numbers, not just measure them.

My typical 1-week engagement

  • Day 1-2: GA4 + BigQuery wiring, consent, page_view, vitals.

  • Day 3: Custom definitions, first Looker Studio dashboard.

  • Day 4: BigQuery p75/p95 queries and route normalization.

  • Day 5: CI guard + interview PDF export.

Measurable outcomes

  • Quantified before/after by route and device.

  • CI gate to prevent regressions.

  • Artifacts you can show in interviews (SQL, dashboard, screenshots).

Related Resources

Key takeaways

  • Real-user monitoring beats synthetic alone—send LCP, INP, CLS, route, and release tags from Angular to GA4 and BigQuery.
  • Define GA4 custom dimensions/metrics up front, then backfill in BigQuery for percentile reporting by route and device.
  • Automate regression checks in CI: fail builds when p75 LCP/INP regress beyond a threshold.
  • Use Consent Mode v2 and non‑PII user properties (role, tenant, release) to stay compliant and segment results.
  • Bring charts to interviews: Looker Studio or an Angular dashboard pulling from BigQuery with PrimeNG/Highcharts.

Implementation checklist

  • Create GA4 property and enable BigQuery export (daily free; streaming may require 360—verify limits).
  • Register custom definitions in GA4: route_path, release_tag, role, tenant_id (hashed), plus numeric metrics for LCP/INP/CLS.
  • Add web‑vitals to Angular and emit GA4 events with event parameters for route and release.
  • Log SPA page_view on router events; include consent mode v2 and region hints.
  • Write BigQuery SQL for p50/p75 by route and release; export to Looker Studio or an internal Angular dashboard.
  • Add a GitHub Actions job to run BigQuery checks and fail on performance regressions.
  • Document your before/after deltas and keep screenshots handy for interviews.

Questions we hear from teams

How much does it cost to hire an Angular developer to set up GA4 + BigQuery?
Most teams land between 10–20 hours for a solid MVP: GA4 wiring, consent mode, web‑vitals, BigQuery SQL, and a basic dashboard. Full CI guardrails and portfolio polish add another 10–20 hours.
How long does an Angular instrumentation engagement take?
A focused setup is typically 3–5 days. If we’re also fixing performance (images, code‑split, SSR, Signals), plan 2–4 weeks to move metrics and lock in CI guards without breaking delivery.
Do I need Firebase or can I use pure GA4?
Either works. Firebase Analytics on web maps to GA4 under the hood. I prefer gtag for control in regulated apps; Firebase adds convenience if you’re already on Firebase Hosting/Remote Config.
Will this affect privacy compliance?
We implement Consent Mode v2, default deny, and only send non‑PII properties (role, hashed tenant, release). Region hints and documentation keep compliance comfortable.
What’s included in a typical Angular consultant engagement?
Discovery, GA4/BigQuery plan, implementation with Signals/SignalStore context, BigQuery queries, Looker Studio setup, and CI guardrails. You get a README and interview‑ready charts within the first week.

Ready to level up your Angular experience?

Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.

Hire Matthew — Remote Angular Expert, Available Now See live Angular apps: NG Wave, gitPlumbers, IntegrityLens, SageStepper

NG Wave

Angular Component Library

A comprehensive collection of 110+ animated, interactive, and customizable Angular components. Converted from React Bits with full feature parity, built with Angular Signals, GSAP animations, and Three.js for stunning visual effects.

Explore Components
NG Wave Component Library

Related resources