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 “we think it’s faster” into charts and percentiles. Wire GA4 + BigQuery into Angular 20+, log Web Vitals and key UX events, and walk into interviews with proof.

I don’t say “we improved INP.” I show the BigQuery chart by route and build, then point to the Signals PR that made it happen.
Back to all posts

The interview hook: “Show me the numbers.”

A familiar scene

Last month a director asked me, “Can you prove the dashboard got faster after the Angular 20 upgrade?” I didn’t open DevTools. I opened a BigQuery sheet, filtered by build hash and route, and showed P90 INP dropping 31% after the Signals refactor. Interviews go differently when you bring numbers.

Context for 2025 teams

As companies plan 2025 Angular roadmaps, you’ll be asked to quantify impact: “What changed and by how much?” This article shows how I instrument Angular apps with GA4 + BigQuery—clean, privacy‑safe, and fast to wire—so you can walk into interviews with charts instead of claims.

  • Angular 20+, Signals, and SSR are table stakes.

  • Leaders expect Core Web Vitals to move, not just stories.

  • RUM beats synthetic: GA4 + BigQuery is the fastest path to interview‑ready proof.

Why Angular teams need real‑user metrics, not just Lighthouse scores

Lighthouse is a lab score; hiring managers want RUM

Lighthouse and CI gates are essential, but they’re snapshots. GA4 + BigQuery captures real users, on real networks, across roles and tenants. That’s how I proved a 28% interaction win on a PrimeNG table for a telecom ads analytics app—production clicks, not lab approximations.

  • Lab is great for regression gates.

  • RUM proves production wins by route, role, and device.

What to measure

Tie every event to a release, route, and role. Then interviewers can ask, “What happened after the Signals migration?” and you can answer with a P90 chart by build hash.

  • Core Web Vitals: LCP, INP, CLS

  • Hydration time (SSR/CSR), route TTI

  • Key UX events: filter, sort, open, save, error, retry

  • Build metadata: build hash, branch, experiment id, role, tenant

Instrument Angular 20+ with GA4: events, Web Vitals, and user properties

<!-- src/index.html -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);} 
  gtag('consent', 'default', { 'ad_storage': 'denied', 'analytics_storage': 'granted' });
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX', { send_page_view: false, anonymize_ip: true });
</script>

If you’re on Firebase Hosting/Analytics, that’s fine too—use the modular SDK and keep the same event schema.

If you’re not on Firebase, use gtag. Add Consent Mode v2 and avoid PII.

Log Core Web Vitals and UX events with a typed AnalyticsService

// analytics.service.ts (Angular 20)
import { Injectable, inject } from '@angular/core';
import { signal, effect } from '@angular/core';
import { createStore } from '@ngrx/signals';
import { onINP, onLCP, onCLS } from 'web-vitals/attribution';

export type UXEvent =
  | { name: 'web_vital'; metric: 'LCP'|'INP'|'CLS'; value: number; route: string; build: string; role?: string }
  | { name: 'table_interaction'; action: 'filter'|'sort'|'paginate'; route: string; value?: number; build: string; role?: string }
  | { name: 'error'; code: string; route: string; build: string; role?: string };

function gtagSend(event: string, params: Record<string, any>) {
  // global gtag from index.html
  // @ts-ignore
  window.gtag?.('event', event, params);
}

@Injectable({ providedIn: 'root' })
export class AnalyticsService {
  private build = (window as any).__APP_BUILD__ || 'local';

  private store = createStore({
    buffered: signal<UXEvent[]>([]),
    online: signal<boolean>(navigator.onLine),
  }, (store) => ({
    add: (e: UXEvent) => store.buffered.update(list => [...list, e]),
    flush: () => {
      store.buffered().forEach(e => this.dispatch(e));
      store.buffered.set([]);
    }
  }));

  constructor() {
    window.addEventListener('online', () => { this.store.online.set(true); this.store.flush(); });
    window.addEventListener('offline', () => this.store.online.set(false));

    // Web Vitals
    onLCP(({ value }) => this.event({ name: 'web_vital', metric: 'LCP', value, route: this.route(), build: this.build }));
    onINP(({ value }) => this.event({ name: 'web_vital', metric: 'INP', value, route: this.route(), build: this.build }));
    onCLS(({ value }) => this.event({ name: 'web_vital', metric: 'CLS', value, route: this.route(), build: this.build }));
  }

  event(e: UXEvent) {
    return navigator.onLine ? this.dispatch(e) : this.store.add(e);
  }

  private dispatch(e: UXEvent) {
    if (e.name === 'web_vital') {
      gtagSend('web_vital', { metric: e.metric, value: e.value, route: e.route, build: e.build });
    } else if (e.name === 'table_interaction') {
      gtagSend('table_interaction', { action: e.action, route: e.route, value: e.value, build: e.build });
    } else if (e.name === 'error') {
      gtagSend('app_error', { code: e.code, route: e.route, build: e.build });
    }
  }

  private route(): string { return location.pathname || '/'; }
}

Use it in a PrimeNG table to measure filter/sort impact:

<p-table [value]="rows" (onFilter)="analytics.event({name:'table_interaction', action:'filter', route:'/reports', build: buildHash})" (onSort)="analytics.event({name:'table_interaction', action:'sort', route:'/reports', build: buildHash})">
</p-table>

2) Capture Web Vitals on app init

I wire this via APP_INITIALIZER or the root layout component in Angular 20+.

  • Low overhead; emit once per load.

  • Attach route/build/role so you can segment later.

3) A typed AnalyticsService with Signals/SignalStore buffering

In kiosk and field apps (airline self‑service, insurance telematics), buffering is non‑negotiable. Signals + SignalStore makes this clean and testable.

  • Offline‑tolerant: buffer and flush on reconnect.

  • Typed params keep BigQuery schemas sane.

Tag events with build hash and inject at CI time

# .github/workflows/build.yml
- name: Build
  run: |
    echo "window.__APP_BUILD__='${{ github.sha }}'" > src/assets/build.js
    npm run build
- name: Attach build script
  run: |
    # ensure index.html loads assets/build.js (small inline or script tag)
<!-- index.html -->
<script src="/assets/build.js"></script>

4) Add a build identifier so you can prove before/after

I inject APP_BUILD in CI so BigQuery can pivot by release in seconds. This is how I showed a 31% INP improvement after a Signals migration for a media network scheduler.

  • Inject at build in CI to avoid code changes.

  • Use the hash in every event.

Export GA4 to BigQuery and run interview‑ready queries

-- P90 INP by route/build (GA4 BigQuery export)
WITH ev AS (
  SELECT
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='metric') AS metric,
    (SELECT value.double_value FROM UNNEST(event_params) WHERE key='value') AS metric_value,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='route') AS route,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='build') AS build,
    event_date
  FROM `myproj.analytics_XXXXXX.events_*`
  WHERE event_name='web_vital' AND _TABLE_SUFFIX BETWEEN '20250101' AND '20251231'
)
SELECT route, build, ROUND( PERCENTILE_CONT(metric_value, 0.90) OVER (PARTITION BY route, build), 0) AS p90_inp
FROM ev
WHERE metric='INP'
GROUP BY route, build, p90_inp
ORDER BY route, build;
-- Funnel: table filter -> row open -> save success within 3 minutes
WITH base AS (
  SELECT
    user_pseudo_id,
    event_timestamp,
    event_name,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='route') AS route,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='build') AS build
  FROM `myproj.analytics_XXXXXX.events_*`
  WHERE event_name IN ('table_interaction','row_open','save_success')
)
SELECT build,
  COUNTIF(event_name='table_interaction') AS filters,
  COUNTIF(event_name='row_open') AS opens,
  COUNTIF(event_name='save_success') AS saves
FROM base
WHERE route='/reports' AND event_timestamp BETWEEN TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY) AND CURRENT_TIMESTAMP()
GROUP BY build
ORDER BY build DESC;

5) Enable BigQuery export

Set daily and streaming export. Within minutes you’ll query real traffic.

  • GA4 Admin → BigQuery Links → Stream to US/EU dataset.

  • You’ll get events_YYYYMMDD tables with JSON payloads.

SQL: P90 INP by route and build

This is the chart interviewers care about—real users, by route, segmented by release.

SQL: Funnel from action to success

I’ve used this to prove a 22% drop in retries after adding optimistic updates + disabled duplicate submits in a payment workflow.

  • Show UX wins: fewer retries, faster time‑to‑save.

  • Correlate with error rates.

A real slice from the field: 28% faster interactions

// Example: debounce user filter to reduce layout thrash and API pressure
filterControl.valueChanges.pipe(debounceTime(150)).subscribe(v => {
  analytics.event({ name:'table_interaction', action:'filter', route:'/reports', build: buildHash });
  table.filter(v, 'name', 'contains');
});

Telecom analytics dashboard

We tagged table filter/sort/paginate, added web-vitals, and stamped events with the build from CI. In the interview packet, one chart showed the drop in P90 INP after the Signals + debounced API change. Another showed fewer retries on save. No guesswork—just BigQuery.

  • PrimeNG table: heavy virtual scroll; millions of rows server‑side.

  • Refactor: Signals for inputs, debounced filters, cache warmers.

  • Result: P90 INP 520ms → 375ms (‑28%), errors ‑18%.

How an Angular Consultant Approaches GA4 + BigQuery Instrumentation

My 5‑day instrumentation sprint (remote)

We do this alongside your app work—no feature freeze. For teams on Nx monorepos, I scope instrumentation to a shared lib and reference it app‑wide. For Firebase apps, I can wire Firebase Analytics with the same event model.

  • Day 1: GA4/BigQuery setup, consent, event schema.

  • Day 2: Web Vitals + route tagging, build hash via CI.

  • Day 3: Key UX events (forms, tables, error/retry).

  • Day 4: SQL notebooks: P90 by route/build; funnels; errors.

  • Day 5: Interview‑ready deck + README on how to maintain.

Tooling I use

Across aviation kiosks and insurance telematics, I’ve paired this with Dockerized hardware simulation, role‑based multi‑tenant apps, and SSR when needed. The pattern’s the same: tag the build, measure the vitals, and show the deltas.

  • Angular 20, Signals/SignalStore

  • PrimeNG & Angular Material

  • Firebase/GA4, BigQuery, web-vitals

  • GitHub Actions/Jenkins/Azure DevOps

  • AWS/Azure/GCP deployment targets

When to Hire an Angular Developer to Prove ROI Fast

Good moments to bring in help

If you need a senior Angular engineer who can wire GA4 + BigQuery and produce readable, defensible charts in a week, bring in an Angular consultant. I’ve done this for Fortune 100 teams across media, airlines, telecom, and insurance.

  • Leadership pressure to justify an Angular 14→20+ migration.

  • You suspect INP/LCP is hurting conversion but need proof.

  • A kiosk/offline flow feels flaky and you can’t reproduce.

  • Upcoming interviews and you need measurable talking points.

What to measure next and how to present it

Add polish that shows up in data

I often add SSR hydration timings in Universal apps and correlate with INP. You can also instrument device state for kiosks—barcode scans, printer ready—with offline buffering via SignalStore.

  • SSR hydration: emit hydration_complete with time_to_hydrate.

  • Retry flows: emit retry with exponential backoff count.

  • A11y: emit focus_trap_miss to trace keyboard traps.

How to show it in interviews

Pair these with Lighthouse CI gates in PRs and you’ll look like a systems thinker, not a “vibe coder.”

  • One slide per metric: P90 INP by route/build; funnel by build; error rate trend.

  • Annotate with change: “Signals migration shipped Jan 12.”

  • Keep a GitHub Gist with the SQL; link in your portfolio.

FAQ: GA4, BigQuery, and hiring

How much does it cost to hire an Angular developer for this?

For a focused instrumentation sprint, expect a fixed scope starting around $6k–$12k depending on app size and environments. I offer a discovery call and a written plan with deliverables in 48 hours.

How long does GA4 + BigQuery setup take?

Typical timeline: 2–3 days to wire GA4, Consent Mode, Web Vitals, build tagging, and basic UX events; 1–2 days for BigQuery queries and an interview‑ready report.

Do we need Firebase?

No. GA4 via gtag.js works great. If you already use Firebase Hosting/Functions or Firebase Analytics, we’ll keep the same event schema—BigQuery export still applies.

Will this impact performance?

Minimal. web-vitals is tiny, events are batched, and we avoid heavy synchronous work on the critical path. We’ll verify in Lighthouse/DevTools and keep bundle budgets intact.

What about privacy/PII?

We never send PII. Use Consent Mode v2, anonymize IP, and restrict roles/tenants to categorical values. I’ll include a redaction checklist in the repo README.

Related Resources

Key takeaways

  • GA4 + BigQuery gives interview‑ready, real‑user evidence: INP/LCP percentiles, route‑level timings, and behavior funnels by release.
  • Tag every event with build hash, route, role, and experiment so you can show before/after deltas during interviews.
  • Use web-vitals + GA4 custom events to capture LCP, INP, CLS in Angular 20+ at app start with zero jank.
  • Create a typed AnalyticsService and buffer events with SignalStore for offline‑tolerant UX.
  • Export to BigQuery and run SQL to produce P90 charts and conversion funnels you can paste into a portfolio.

Implementation checklist

  • Create GA4 property and enable BigQuery export (streaming).
  • Add Consent Mode v2 and gtag to Angular index.html (or Firebase Analytics if you’re on Firebase).
  • Install web-vitals and emit LCP/INP/CLS as GA4 events.
  • Build a typed AnalyticsService; tag events with build, route, role, and experiment.
  • Buffer events with SignalStore and flush on connectivity regain.
  • Send key UX events: filter, sort, open, save, error, retry, SSR hydration complete.
  • Ship to a preview env; verify events in GA4 DebugView and BigQuery streaming tables.
  • Run SQL: P90 INP by route and build; funnel from action to success; error correlation.
  • Add a “Metrics” slide to your interview deck with before/after charts.
  • Keep a minimal redaction policy—no PII; use user roles/tenants as categorical dimensions only.

Questions we hear from teams

How much does it cost to hire an Angular developer for GA4 + BigQuery instrumentation?
A focused sprint typically starts at $6k–$12k depending on scope and environments. I provide a discovery call and a written plan with deliverables within 48 hours.
How long does an Angular instrumentation engagement take?
Plan for 3–5 business days: GA4/Consent/Web Vitals wiring, build tagging, key UX events, and BigQuery queries. Add 1–2 days if we include SSR hydration metrics and dashboards.
Do we need Firebase to use GA4?
No. You can use gtag.js directly. If you already use Firebase, we’ll use Firebase Analytics with the same event schema and keep BigQuery export for analysis.
Will GA4 impact Angular performance?
Properly configured, the overhead is negligible. We defer heavy work, batch events, and verify via Lighthouse and Core Web Vitals. Bundle budgets remain under control.
What’s included in a typical engagement?
GA4 + Consent Mode, Web Vitals, typed AnalyticsService, SignalStore buffering, CI build tagging, BigQuery export, 3–5 query templates, and an interview‑ready slide deck with before/after charts.

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 Request a 30‑Minute Instrumentation Review

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