@soasap-com/angular-sdk
SOASAP Angular SDK
Lightweight, production-ready feature flags SDK for Angular 17+ — O(1) local evaluation,
local-first browser caching, real-time SSE updates, reactive Signals,
zero runtime dependencies, and graceful offline behavior.
Standalone components and bootstrapApplication.
Installation
Install from npm.
Requires @angular/core 17+ as a peer dependency. Supports Angular 17, 18, and 19+.
npm install @soasap-com/angular-sdk
Quick start
Register with provideSoasap() at bootstrap, enable
preload: true for non-blocking startup sync,
and inject SoasapService to read reactive Signals in components.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideSoasap } from '@soasap-com/angular-sdk';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideSoasap({
apiKey: environment.soasapApiKey,
preload: true,
}),
],
});
// checkout.component.ts
import { Component, inject, type Signal } from '@angular/core';
import { SoasapService } from '@soasap-com/angular-sdk';
@Component({
selector: 'app-checkout',
template: `
@if (newCheckout()) {
<app-new-checkout />
} @else {
<app-legacy-checkout />
}
`,
})
export class CheckoutComponent {
private readonly soasap = inject(SoasapService);
readonly newCheckout: Signal<boolean> = this.soasap.bool('new-checkout');
}
Imperative client (no Signals)
Use createSoasapClient() for sync reads outside Angular components
— guards, interceptors, or non-UI code paths.
import { createSoasapClient } from '@soasap-com/angular-sdk';
const flags = createSoasapClient({
apiKey: environment.soasapApiKey,
preload: true,
});
if (flags.getBool('new-checkout')) {
console.log('New checkout enabled');
}
Typed access
SoasapService methods return Angular Signals —
read them in templates with () (e.g. featureX()).
JSON keys from the dashboard are returned as stored (typically camelCase).
// Signals (reactive)
this.soasap.bool('feature-x');
this.soasap.number('rate-limit', 100);
this.soasap.string('ui-theme', 'light');
this.soasap.json<CheckoutConfig>('checkout-config');
// Client (sync)
flags.getBool('feature-x');
flags.getNumber('rate-limit', 100);
flags.getString('ui-theme', 'light');
flags.getJson<CheckoutConfig>('checkout-config');
Startup sync
preload: true (recommended) starts the SSE worker in the background,
loads the browser cache on cold start, and never blocks Angular bootstrap.
Without it, lazy mode defers the network connection until the first flag read.
In lazy mode, the first evaluation uses default values (or cached flags from
localStorage if available) while SSE connects in the background.
// Immediate sync (recommended)
provideSoasap({ apiKey: '...', preload: true })
// Lazy sync — first read falls back to defaults or storage cache
provideSoasap({ apiKey: '...' })
Shared client instance
Create one client per browser tab and pass it to provideSoasap(client).
Pre-built clients are not closed automatically — call close() yourself.
import { createSoasapClient, provideSoasap } from '@soasap-com/angular-sdk';
const flags = createSoasapClient({
apiKey: environment.soasapApiKey,
preload: true,
});
bootstrapApplication(AppComponent, {
providers: [provideSoasap(flags)],
});
Error handling & observability
Hook background diagnostics without affecting the hot path.
Sources: Network, Storage, Parser.
import { SoasapErrorSource, provideSoasap } from '@soasap-com/angular-sdk';
provideSoasap({
apiKey: '...',
onError: (ctx) => {
console.error(
`[${ctx.source}] transient=${ctx.isTransient}`,
ctx.exception.message);
},
});
// SoasapErrorSource.Network | .Storage | .Parser
Production safety & guardrails
- Immutable snapshots — atomic reference swap; readers and signals never see partial updates
- Memory cap protection (anti-DoS) — 5 MB SSE payload cap; oversized streams are dropped and reset
- Payload validation — root element must be a JSON object
{}; invalid payloads are ignored - Storage debounce — browser writes coalesced at most once every ~2.5 seconds
- Zero runtime dependencies —
@angular/coreis the only peer dependency - One client per tab — create one client per browser tab or app shell
Getters and signals never throw (except missing provider when injecting SoasapService).
Offline resiliency
| Scenario | Behavior |
|---|---|
| API unavailable | Uses stale cached flags |
| SSE disconnected | Keeps last known snapshot |
| First visit without cache | Returns default values |
| Invalid payload | Payload ignored |
| Storage quota / failure | In-memory mode continues |
| Persistent network issues | Automatic reconnect with backoff |
Browser storage key: soasap:cache:<api-key-prefix>.
Override with cacheKeyPrefix: 'myapp:flags'.
Graceful shutdown
provideSoasap({ apiKey, ... }) calls close() automatically when the injector is destroyed.
Pre-built clients passed to provideSoasap(client) are not closed automatically —
call await flags.close() when done.
Supported environments
- Angular 17 / 18 / 19+
- Modern browsers with
fetch,ReadableStream, andlocalStorage - Standalone components and
bootstrapApplication