@savvagent/angular
v1.0.1
Published
Angular SDK for Savvagent feature flags
Downloads
94
Maintainers
Readme
@savvagent/angular
Angular SDK for Savvagent - AI-powered feature flags that prevent production incidents.
Installation
npm install @savvagent/angular
# or
pnpm add @savvagent/angular
# or
yarn add @savvagent/angularQuick Start
1. Import SavvagentModule in your app
// app.module.ts
import { SavvagentModule } from '@savvagent/angular';
@NgModule({
imports: [
SavvagentModule.forRoot({
config: {
apiKey: 'sdk_your_api_key_here',
applicationId: 'your-app-id', // Optional: for application-scoped flags
enableRealtime: true, // Enable real-time flag updates
},
defaultContext: {
environment: 'production',
userId: 'user-123', // Optional: set default user
}
})
]
})
export class AppModule {}2. Use SavvagentService in your components
import { Component } from '@angular/core';
import { SavvagentService } from '@savvagent/angular';
@Component({
selector: 'app-my-feature',
template: `
<ng-container *ngIf="newFeature$ | async as flag">
<app-spinner *ngIf="flag.loading"></app-spinner>
<app-new-checkout *ngIf="flag.value"></app-new-checkout>
<app-old-checkout *ngIf="!flag.value && !flag.loading"></app-old-checkout>
</ng-container>
`
})
export class MyFeatureComponent {
newFeature$ = this.savvagent.flag$('new-checkout-flow', {
defaultValue: false,
realtime: true,
});
constructor(private savvagent: SavvagentService) {}
}Standalone Components (Angular 14+)
For standalone components, you can use importProvidersFrom:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { importProvidersFrom } from '@angular/core';
import { SavvagentModule } from '@savvagent/angular';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
SavvagentModule.forRoot({
config: { apiKey: 'sdk_your_api_key' }
})
)
]
});API Reference
SavvagentModule
Angular module that configures the Savvagent SDK.
SavvagentModule.forRoot(config)
Configure the module with your API key and default context.
interface SavvagentConfig {
config: FlagClientConfig;
defaultContext?: DefaultFlagContext;
}
interface FlagClientConfig {
/** SDK API key (starts with sdk_) */
apiKey: string;
/** Application ID for application-scoped flags */
applicationId?: string;
/** Base URL for the Savvagent API */
baseUrl?: string;
/** Enable real-time flag updates via SSE (default: true) */
enableRealtime?: boolean;
/** Cache TTL in milliseconds (default: 60000) */
cacheTtl?: number;
/** Enable telemetry tracking (default: true) */
enableTelemetry?: boolean;
/** Default flag values when evaluation fails */
defaults?: Record<string, boolean>;
/** Custom error handler */
onError?: (error: Error) => void;
}
interface DefaultFlagContext {
applicationId?: string;
environment?: string;
organizationId?: string;
userId?: string;
anonymousId?: string;
sessionId?: string;
language?: string;
attributes?: Record<string, any>;
}SavvagentService
Injectable service that provides all feature flag functionality.
Properties
ready$: Observable<boolean>- Observable that emits true when the client is readyisReady: boolean- Check if the client is ready synchronouslyflagClient: FlagClient | null- Access the underlying FlagClient for advanced use cases
flag$(flagKey, options)
Get a reactive Observable for a feature flag with automatic updates.
interface FlagOptions {
/** Context for flag evaluation (user_id, attributes, etc.) */
context?: FlagContext;
/** Default value to use while loading or on error */
defaultValue?: boolean;
/** Enable real-time updates for this flag (default: true) */
realtime?: boolean;
}
interface FlagObservableResult {
/** Current flag value */
value: boolean;
/** Whether the flag is currently being evaluated */
loading: boolean;
/** Error if evaluation failed */
error: Error | null;
/** Detailed evaluation result */
result: FlagEvaluationResult | null;
}Example:
@Component({
template: `
<ng-container *ngIf="betaFeature$ | async as flag">
<div *ngIf="flag.loading">Loading...</div>
<div *ngIf="flag.error">Error: {{ flag.error.message }}</div>
<app-beta *ngIf="flag.value"></app-beta>
<app-standard *ngIf="!flag.value && !flag.loading"></app-standard>
</ng-container>
`
})
export class MyComponent {
betaFeature$ = this.savvagent.flag$('beta-feature', {
context: {
user_id: this.userId,
attributes: { plan: 'pro' }
},
defaultValue: false,
realtime: true
});
constructor(private savvagent: SavvagentService) {}
}flagValue$(flagKey, options)
Get just the boolean value as an Observable. Useful when you don't need loading/error states.
@Component({
template: `
<button *ngIf="isFeatureEnabled$ | async">New Button</button>
`
})
export class SimpleComponent {
isFeatureEnabled$ = this.savvagent.flagValue$('my-feature');
constructor(private savvagent: SavvagentService) {}
}evaluate(flagKey, context)
Evaluate a feature flag once (non-reactive).
async checkFeature() {
const result = await this.savvagent.evaluate('new-feature');
console.log(result.value, result.reason);
}isEnabled(flagKey, context)
Simple boolean check if a flag is enabled.
async doSomething() {
if (await this.savvagent.isEnabled('feature-flag')) {
// Feature is enabled
}
}withFlag(flagKey, callback, context)
Execute code conditionally based on flag value.
async trackPageView() {
await this.savvagent.withFlag('analytics-enabled', async () => {
await this.analytics.track('page_view');
});
}trackError(flagKey, error, context)
Track errors with flag context for AI-powered analysis.
handleError(error: Error) {
this.savvagent.trackError('new-payment-flow', error);
}User Management
// Set user ID for logged-in users
setUserId(userId: string | null): void;
getUserId(): string | null;
// Anonymous ID management
getAnonymousId(): string | null;
setAnonymousId(id: string): void;Local Overrides
For development and testing:
// Set override (takes precedence over server values)
setOverride(flagKey: string, value: boolean): void;
// Clear overrides
clearOverride(flagKey: string): void;
clearAllOverrides(): void;
// Check overrides
hasOverride(flagKey: string): boolean;
getOverride(flagKey: string): boolean | undefined;
getOverrides(): Record<string, boolean>;
// Set multiple overrides
setOverrides(overrides: Record<string, boolean>): void;Flag Discovery
// Get all flags (returns Observable)
getAllFlags$(environment?: string): Observable<FlagDefinition[]>;
// Get all flags (Promise-based)
getAllFlags(environment?: string): Promise<FlagDefinition[]>;
// Get enterprise-scoped flags only
getEnterpriseFlags(environment?: string): Promise<FlagDefinition[]>;Cache & Connection
clearCache(): void;
isRealtimeConnected(): boolean;
close(): void;Advanced Examples
User Targeting
@Component({...})
export class UserFeatureComponent implements OnInit {
premiumFeature$!: Observable<FlagObservableResult>;
constructor(
private savvagent: SavvagentService,
private auth: AuthService
) {}
ngOnInit() {
this.premiumFeature$ = this.savvagent.flag$('premium-features', {
context: {
user_id: this.auth.userId,
attributes: {
plan: this.auth.userPlan,
signupDate: this.auth.signupDate
}
}
});
}
}Dynamic Initialization
If you need to initialize the service after getting user data:
@Component({...})
export class AppComponent implements OnInit {
constructor(
private savvagent: SavvagentService,
private auth: AuthService
) {}
ngOnInit() {
// Wait for auth, then initialize
this.auth.user$.pipe(take(1)).subscribe(user => {
this.savvagent.initialize({
config: {
apiKey: environment.savvagentApiKey
},
defaultContext: {
userId: user?.id,
environment: environment.name
}
});
});
}
}Error Tracking
@Component({...})
export class PaymentComponent {
constructor(private savvagent: SavvagentService) {}
async processPayment() {
try {
const result = await this.paymentService.process();
return result;
} catch (error) {
// Error is correlated with flag changes
this.savvagent.trackError('new-payment-flow', error as Error);
throw error;
}
}
}A/B Testing
@Component({
template: `
<app-checkout-a *ngIf="!(variantB$ | async)"></app-checkout-a>
<app-checkout-b *ngIf="variantB$ | async"></app-checkout-b>
`
})
export class ABTestComponent {
variantB$ = this.savvagent.flagValue$('checkout-variant-b', {
context: {
user_id: this.userId // Consistent assignment per user
}
});
constructor(private savvagent: SavvagentService) {}
}Development Override Panel
@Component({
selector: 'app-flag-overrides',
template: `
<div *ngFor="let flag of flags$ | async">
<label>
<input
type="checkbox"
[checked]="savvagent.getOverride(flag.key) ?? flag.enabled"
(change)="toggleOverride(flag.key, $event)"
/>
{{ flag.key }}
</label>
<button (click)="clearOverride(flag.key)">Reset</button>
</div>
`
})
export class FlagOverridesComponent implements OnInit {
flags$ = this.savvagent.getAllFlags$('development');
constructor(public savvagent: SavvagentService) {}
toggleOverride(flagKey: string, event: Event) {
const checked = (event.target as HTMLInputElement).checked;
this.savvagent.setOverride(flagKey, checked);
}
clearOverride(flagKey: string) {
this.savvagent.clearOverride(flagKey);
}
}TypeScript Support
This package is written in TypeScript and provides full type definitions.
import type {
FlagClientConfig,
FlagContext,
FlagEvaluationResult,
FlagDefinition,
SavvagentConfig,
DefaultFlagContext,
FlagObservableResult,
FlagOptions,
} from '@savvagent/angular';Best Practices
Import SavvagentModule.forRoot() in your root module to ensure a single instance of the service.
Use the
defaultValueoption to provide a safe fallback while flags are loading.Enable real-time updates for flags that change frequently or require immediate propagation.
Track errors in new features to leverage Savvagent's AI-powered error correlation.
Use user context for targeted rollouts based on user attributes, location, or behavior.
Handle loading states gracefully using the async pipe and conditional rendering.
Use
flagValue$when you only need the boolean value without loading/error states.Clean up subscriptions - the service handles cleanup automatically on destroy, but use
takeUntilor similar patterns in components for long-lived subscriptions.
License
MIT
