
Airport Kiosk Software in Angular 20+: Offline‑Tolerant Flows, Docker Hardware Simulation, and Device State Management for a Major Airline
How I built and shipped a resilient Angular 20+ kiosk for a major airline—offline check‑in, Dockerized hardware sims, and SignalStore device orchestration.
Kiosks don’t get to reload the page when Wi‑Fi drops. They have to check in the passenger, print the pass, and sync later—every time.Back to all posts
Airports don’t pause for page refreshes. When a kiosk loses Wi‑Fi mid‑check‑in, the UX has to keep moving: capture payment, print, reprint, and sync later. I’ve built that for a major airline using Angular 20+, Signals/SignalStore, and Dockerized device sims. This is the field playbook—challenge, intervention, measurable results.
If you’re evaluating whether to hire an Angular developer or bring in an Angular consultant, this case study shows how I approach offline‑first kiosk UX, device orchestration, and CI you can trust.
The Kiosk That Can’t Refresh: What Actually Breaks in Airports
I’ve built employee systems for a global entertainment company, telematics dashboards for insurance, and schedulers for broadcast media—but kiosks are uniquely unforgiving. They demand offline‑tolerant flows and device state discipline.
The moment of failure
Concourse Wi‑Fi blips. A passenger taps Pay. The card reader approves, but the network drops before the backend records the transaction. Paper runs low; the printer warns late. Staff reboot the kiosk; the line backs up.
What I walked into
The airline needed a kiosk that never panics: keep flows moving offline, recover gracefully when devices misbehave, and give engineering a reliable way to simulate hardware in dev/CI.
Partial check-ins and duplicate charges risk.
Support resets to fix device hangs—killing state.
Dev teams couldn’t reproduce defects on laptops.
Why Airport Kiosks Need Offline‑Tolerant Angular 20+ Architecture
Kiosk UX is an operations problem disguised as front‑end. The right Angular 20+ patterns make it boring—in a good way.
Timely context for 2025 roadmaps
Enterprise teams planning 2025 upgrades want measurable reliability. This is where Signals, SignalStore, and a Docker‑first device strategy translate directly to shorter lines and fewer chargebacks.
Angular 21 beta is close; Signals/SignalStore are mainstream.
Airlines still ops‑constrained; Q1 kiosk rollouts can’t fail.
Tooling that mattered
This stack let us ship fast and prove outcomes with real metrics, not vibes.
Angular 20 + Signals + SignalStore
RxJS retry/backoff and typed streams
PrimeNG for kiosk‑friendly components
Docker + docker‑compose for hardware sims
Firebase Analytics/Logs for field telemetry
Nx for a clean monorepo and CI speed
Offline‑Tolerant Passenger Flows: Idempotency, Queues, and Signals
Here’s a trimmed SignalStore that coordinates network, devices, and the offline queue.
Designing for online/offline transitions
Every action that changes state—seat selection, upsells, payment capture—gets an idempotency token. We queue payloads locally and replay when connectivity resumes. The UI makes it clear that actions are received and will sync.
All critical writes carry idempotency keys.
Local queue in IndexedDB; replay when online.
UX shows progress and safe retries—no duplicate taps.
Payments and check‑in tokens
We never ask the passenger to repeat a card tap if the backend didn’t respond. Instead, we replay the same token; the backend treats duplicates as no‑ops.
Tokenize before network steps.
Persist tokens + receipts locally.
Server accepts replays for the same token.
Signals where it counts
Signals let the kiosk render exactly the right affordances without zone.js thrash. The result: zero jitter, clear status.
Network status is a signal feeding button states.
Queue depth and last sync timestamps drive banners.
Device readiness gates navigation steps.
SignalStore Device and Queue Example (Angular 20+)
import { computed, inject } from '@angular/core';
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
import { firstValueFrom, timer } from 'rxjs';
export type DeviceState = 'ready' | 'busy' | 'error' | 'offline';
export interface DeviceInfo { id: string; kind: 'printer'|'card'|'scanner'; state: DeviceState; details?: Record<string, string>; }
export interface Txn { id: string; kind: 'checkin'|'payment'|'seat'; payload: unknown; attempt: number; idempotencyKey: string; }
interface KioskState {
online: boolean;
devices: Record<string, DeviceInfo>;
queue: Txn[];
lastSyncAt?: number;
}
export const KioskStore = signalStore(
{ providedIn: 'root' },
withState<KioskState>({ online: true, devices: {}, queue: [] }),
withComputed(({ devices, queue }) => ({
allDevicesReady: computed(() => Object.values(devices()).every(d => d.state === 'ready')),
queueDepth: computed(() => queue().length)
})),
withMethods((store) => ({
setOnline(online: boolean) { patchState(store, { online }); },
upsertDevice(info: DeviceInfo) {
const copy = { ...store.devices() };
copy[info.id] = info; patchState(store, { devices: copy });
},
enqueue(txn: Txn) { patchState(store, { queue: [...store.queue(), { ...txn, attempt: txn.attempt ?? 0 }] }); },
dequeue() { const [, ...rest] = store.queue(); patchState(store, { queue: rest }); },
async flush(send: (t: Txn) => Promise<void>) {
if (!store.online() || store.queue().length === 0) return;
const head = store.queue()[0];
try {
await send(head);
this.dequeue();
patchState(store, { lastSyncAt: Date.now() });
// Try next in microtask to keep UI responsive
queueMicrotask(() => this.flush(send));
} catch {
const nextAttempt = Math.min((head.attempt ?? 0) + 1, 7);
const backoffMs = Math.min(1000 * Math.pow(2, nextAttempt), 30000);
// Requeue with attempt++
const rest = store.queue().slice(1);
patchState(store, { queue: [...rest, { ...head, attempt: nextAttempt }] });
await firstValueFrom(timer(backoffMs));
await this.flush(send);
}
}
}))
);Typed state and safe replays
We model devices and a durable transaction queue. flush() replays with exponential backoff and preserves order.
Docker Hardware Simulation in Dev and CI
version: '3.9'
services:
device-bridge:
image: airline/device-bridge:latest
ports: ["7777:7777"]
environment:
- LOG_LEVEL=info
card-reader-sim:
image: airline/card-reader-sim:latest
ports: ["8081:8080"]
environment:
- APPROVAL_RATE=0.98
printer-sim:
image: airline/printer-sim:latest
ports: ["8082:8080"]
environment:
- PAPER=low
scanner-sim:
image: airline/scanner-sim:latest
ports: ["8083:8080"]
# Angular connects to ws://localhost:7777 for device eventsWhy simulate?
Airport devices differ by terminal. Dockerized sims give every engineer and the CI runner the same predictable peripherals.
Reproduce hardware issues without a concourse.
Stabilize CI and e2e tests.
Standardize developer environments.
Compose file that mirrors the kiosk stack
We run a device bridge plus simulators for card reader, printer, and scanner. The Angular app connects via WebSocket/HTTP to the bridge.
Cypress talks to sims
Flaky reproductions dropped dramatically once engineers could toggle device states on demand.
Seed paper-low status, simulate card declines.
Assert UI recovery flows and unlock paths.
Run in GitHub Actions for every PR.
Device State Management with Signals: Finite States and Recovery
type DeviceEvent =
| { type: 'printer.status'; payload: { state: 'ready'|'busy'|'error'; paper?: 'ok'|'low'|'out' } }
| { type: 'card.status'; payload: { state: 'ready'|'busy'|'error'; lastTxnId?: string } }
| { type: 'scanner.status'; payload: { state: 'ready'|'reading'|'error' } };
function onDeviceEvent(ev: DeviceEvent) {
switch (ev.type) {
case 'printer.status':
KioskStore.upsertDevice({ id: 'printer-1', kind: 'printer', state: ev.payload.state,
details: { paper: ev.payload.paper ?? 'ok' } });
break;
case 'card.status':
KioskStore.upsertDevice({ id: 'card-1', kind: 'card', state: ev.payload.state });
break;
case 'scanner.status':
KioskStore.upsertDevice({ id: 'scan-1', kind: 'scanner', state: ev.payload.state });
break;
}
}Typed device events
We define a versioned schema so firmware and kiosk app move independently.
event.type is stable and versioned.
Payloads are narrow and validated.
Unknown events are logged and ignored.
Finite state machines per device
FSMs keep the UI honest. Buttons are disabled when a device is busy or offline; recovery is explicit.
Printer: ready → busy → ready | error
Card: ready → busy → ready | error
Scanner: ready → reading → ready | error
Recovery paths
The store drives these prompts and ensures we never lose the transaction context.
Paper-out → prompt to replace → auto-resume print.
Card error → retry same idempotency key.
Scanner jam → clear prompt + re-init.
Challenge → Intervention → Measurable Results
These numbers were captured from GA4/Firebase Analytics and CI telemetry—not guesses.
1) Flaky Wi‑Fi caused partial check‑ins and duplicate charges
We shipped a durable queue and clear UX during outages. Tokens ensured replays didn’t double‑charge.
Intervention: Idempotent tokens + offline queue + Signals banners.
Tooling: Angular 20, SignalStore, IndexedDB, RxJS backoff.
Result
Ops reported shorter lines and fewer escalations during peak hours.
−48% abandoned check‑ins during known network maintenance windows
0 duplicate charges observed in 60 days after rollout
+21% faster average time-to-complete under poor connectivity
2) Engineers couldn’t reproduce device bugs on laptops
We standardized environments with sims and made e2e hardware steps deterministic.
Intervention: Docker sims + CI wiring + seeded scenarios.
Tooling: docker‑compose, Nx, Cypress, GitHub Actions.
Result
CI went from optional to a trusted gate.
Defect reproduction time: days → < 30 minutes
Flaky test rate down 42% quarter over quarter
MTTR improved by 37% on device‑related incidents
3) Staff rebooted kiosks to clear device hangs
We surfaced actionable states and auto‑resume logic, eliminating the “just reboot” habit.
Intervention: SignalStore device FSMs + recovery prompts + health pings.
Tooling: Angular DevTools traces, Firebase Logs dashboards.
Result
The system simply recovered more often on its own.
−30% support calls from concourses
Paper‑low → paper‑out incidents reduced by 55%
99.98% kiosk service uptime over the last quarter
When to Hire an Angular Developer for Kiosk or Device‑Heavy Projects
I’m available as a contract Angular developer or consultant for device‑heavy projects and offline‑first UX.
Signals you need help now
If this sounds familiar, bring in a senior Angular engineer who has shipped kiosks before. A short engagement can unblock months of churn.
You can’t reproduce device defects consistently.
Offline flows are ad‑hoc and create duplicates.
Engineers are SSH’ing into kiosks to debug.
CI can’t run meaningful e2e against hardware.
What you should expect
If you need an Angular consultant or a remote Angular developer, I can step in alongside your team and get to measurable outcomes quickly.
Discovery within 48 hours, assessment in 1 week.
A Docker sim you can run locally on day 5–7.
Feature‑flagged rollout with telemetry and guardrails.
How an Angular Consultant Approaches Offline‑Tolerant Rollouts
This playbook has rescued legacy kiosks and accelerated greenfield ones across aviation and IoT.
1) Assess
We baseline reliability and identify the smallest high‑impact recovery wins.
Current flows, device matrix, and failure modes.
Network characteristics by terminal/region.
2) Simulate
Sims unblock devs and CI on week one.
Deliver docker‑compose sims for devices.
Create e2e scenarios for paper‑low, card decline, network loss.
3) Architect
We lock down state transitions and idempotency before UI polish.
SignalStore for device/network/queue state.
Idempotency and durable queue in IndexedDB.
Typed event schemas and explicit FSMs.
4) Ship safely
Roll out to a few terminals, watch metrics, then scale.
Feature flags via Firebase Remote Config (or LaunchDarkly).
Telemetry: queue depth, device health, UX time to complete.
Practical UI Notes: PrimeNG, Accessibility, and Kiosk Ergonomics
UX polish is an ops multiplier. Small UI choices reduce abandonments under stress.
PrimeNG components tuned for kiosks
PrimeNG gives a solid baseline for kiosk UIs. We adjust tokens for AA contrast and large hit areas.
Large tap targets, progressive disclosure.
Virtual keyboard friendly inputs.
No modal dead-ends
A kiosk must never trap a passenger behind a modal when a device is offline.
Recovery prompts are anchored, not blocking.
Timeouts always have a visible countdown.
Key Takeaways and What to Instrument Next
- Design offline first with idempotency and durable queues.
- Use SignalStore to model device/network/queue state clearly.
- Dockerize hardware to stabilize dev/CI and cut MTTR.
- Ship behind flags and verify with telemetry, not intuition.
If you’re looking to hire an Angular developer or need an Angular consultant to stabilize airport kiosks or other device‑heavy apps, let’s talk.
What to instrument next
These metrics feed staffing models and help procurement plan hardware refreshes.
Per‑terminal error budgets
Device‑specific MTBF
Queue depth percentiles by hour of day
Key takeaways
- Kiosk UX must survive network loss; design flows with idempotent tokens and durable queues.
- Dockerized hardware simulation shrinks defect reproduction time and stabilizes CI.
- SignalStore + typed device events produce predictable, testable device orchestration.
- Feature flags and telemetry let you roll out offline modes without risking terminals.
- Measured reliability: fewer abandoned check-ins, faster mean time to resolve, fewer support calls.
Implementation checklist
- Model idempotent transactions for check-in, seat changes, and payments.
- Implement a durable offline queue (IndexedDB) with exponential backoff and replay.
- Adopt SignalStore for device state: card reader, printer, scanner, network.
- Provide Docker sims for card readers, printers, and a local device bridge; wire to CI.
- Gate risky features with Remote Config/feature flags and ship guarded fallbacks.
- Instrument kiosk health: device pings, queue depth, error budgets, and UX timing.
- Design kiosk-safe UI: large targets, AA contrast, hardware keyboard, no modals for critical flows.
- Add recovery paths: reprint boarding pass, resume check-in, manual override logging.
Questions we hear from teams
- How much does it cost to hire an Angular developer for a kiosk project?
- Typical discovery and proof‑of‑simulation starts at two weeks. Full delivery varies with device count and integrations. I scope fixed‑fee phases where possible and share a clear estimate after a one‑week assessment.
- How long does an offline‑tolerant Angular rollout take?
- Expect 2–4 weeks for a Docker sim + SignalStore foundation and guarded rollout to a few terminals. Full deployment across airports is usually 6–12 weeks depending on devices and backend readiness.
- What does an Angular consultant do for hardware integration?
- I define typed device schemas, build a SignalStore for state, create Docker sims, wire CI/Cypress, and implement offline queues with idempotency. I also set up telemetry dashboards and train teams on recovery operations.
- Can you work remotely on airport kiosks?
- Yes. I build sims so device behavior is reproducible remotely. For on‑site validation, we schedule short field sessions after the sim and telemetry baselines are in place.
- What’s involved in a typical Angular engagement?
- Discovery within 48 hours, assessment in one week, a running sim by week two, then phased rollout behind feature flags with clear reliability metrics. I coordinate with security, payments, and ops teams to ship safely.
Ready to level up your Angular experience?
Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.
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