@savvagent/angular 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +484 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/module.ts.html +289 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/service.ts.html +1846 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +242 -0
- package/coverage/module.ts.html +289 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/service.ts.html +1846 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/README.md +484 -0
- package/dist/esm2022/index.mjs +15 -0
- package/dist/esm2022/module.mjs +75 -0
- package/dist/esm2022/savvagent-angular.mjs +5 -0
- package/dist/esm2022/service.mjs +473 -0
- package/dist/fesm2022/savvagent-angular.mjs +563 -0
- package/dist/fesm2022/savvagent-angular.mjs.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/module.d.ts +57 -0
- package/dist/service.d.ts +319 -0
- package/jest.config.js +40 -0
- package/ng-package.json +8 -0
- package/package.json +73 -0
- package/setup-jest.ts +2 -0
- package/src/index.spec.ts +144 -0
- package/src/index.ts +38 -0
- package/src/module.spec.ts +283 -0
- package/src/module.ts +68 -0
- package/src/service.spec.ts +945 -0
- package/src/service.ts +587 -0
- package/test-utils/angular-core-mock.ts +28 -0
- package/test-utils/angular-testing-mock.ts +87 -0
- package/tsconfig.json +33 -0
- package/tsconfig.spec.json +11 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @savvagent/angular
|
|
2
|
+
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updates for new API parameters
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @savvagent/sdk@1.0.1
|
|
10
|
+
|
|
11
|
+
## 1.0.0
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- Initial release of Angular SDK for Savvagent feature flags
|
|
16
|
+
- `SavvagentModule` - Angular module for easy SDK configuration with `forRoot()`
|
|
17
|
+
- `SavvagentService` - Injectable service with full feature flag functionality
|
|
18
|
+
- Reactive API with RxJS Observables (`flag$`, `flagValue$`, `getAllFlags$`)
|
|
19
|
+
- Promise-based API for non-reactive use cases (`evaluate`, `isEnabled`, `withFlag`)
|
|
20
|
+
- Real-time flag updates via SSE subscription
|
|
21
|
+
- Local override support for development and testing
|
|
22
|
+
- User identification and anonymous ID management
|
|
23
|
+
- Error tracking with flag context for AI-powered analysis
|
|
24
|
+
- Full TypeScript support with comprehensive type definitions
|
|
25
|
+
- Support for Angular 14+ (including standalone components)
|
|
26
|
+
- Default context configuration for application-wide settings
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Savvagent, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# @savvagent/angular
|
|
2
|
+
|
|
3
|
+
Angular SDK for Savvagent - AI-powered feature flags that prevent production incidents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @savvagent/angular
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @savvagent/angular
|
|
11
|
+
# or
|
|
12
|
+
yarn add @savvagent/angular
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Import SavvagentModule in your app
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// app.module.ts
|
|
21
|
+
import { SavvagentModule } from '@savvagent/angular';
|
|
22
|
+
|
|
23
|
+
@NgModule({
|
|
24
|
+
imports: [
|
|
25
|
+
SavvagentModule.forRoot({
|
|
26
|
+
config: {
|
|
27
|
+
apiKey: 'sdk_your_api_key_here',
|
|
28
|
+
applicationId: 'your-app-id', // Optional: for application-scoped flags
|
|
29
|
+
enableRealtime: true, // Enable real-time flag updates
|
|
30
|
+
},
|
|
31
|
+
defaultContext: {
|
|
32
|
+
environment: 'production',
|
|
33
|
+
userId: 'user-123', // Optional: set default user
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
export class AppModule {}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Use SavvagentService in your components
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { Component } from '@angular/core';
|
|
45
|
+
import { SavvagentService } from '@savvagent/angular';
|
|
46
|
+
|
|
47
|
+
@Component({
|
|
48
|
+
selector: 'app-my-feature',
|
|
49
|
+
template: `
|
|
50
|
+
<ng-container *ngIf="newFeature$ | async as flag">
|
|
51
|
+
<app-spinner *ngIf="flag.loading"></app-spinner>
|
|
52
|
+
<app-new-checkout *ngIf="flag.value"></app-new-checkout>
|
|
53
|
+
<app-old-checkout *ngIf="!flag.value && !flag.loading"></app-old-checkout>
|
|
54
|
+
</ng-container>
|
|
55
|
+
`
|
|
56
|
+
})
|
|
57
|
+
export class MyFeatureComponent {
|
|
58
|
+
newFeature$ = this.savvagent.flag$('new-checkout-flow', {
|
|
59
|
+
defaultValue: false,
|
|
60
|
+
realtime: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
constructor(private savvagent: SavvagentService) {}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Standalone Components (Angular 14+)
|
|
68
|
+
|
|
69
|
+
For standalone components, you can use `importProvidersFrom`:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// main.ts
|
|
73
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
74
|
+
import { importProvidersFrom } from '@angular/core';
|
|
75
|
+
import { SavvagentModule } from '@savvagent/angular';
|
|
76
|
+
import { AppComponent } from './app/app.component';
|
|
77
|
+
|
|
78
|
+
bootstrapApplication(AppComponent, {
|
|
79
|
+
providers: [
|
|
80
|
+
importProvidersFrom(
|
|
81
|
+
SavvagentModule.forRoot({
|
|
82
|
+
config: { apiKey: 'sdk_your_api_key' }
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API Reference
|
|
90
|
+
|
|
91
|
+
### `SavvagentModule`
|
|
92
|
+
|
|
93
|
+
Angular module that configures the Savvagent SDK.
|
|
94
|
+
|
|
95
|
+
#### `SavvagentModule.forRoot(config)`
|
|
96
|
+
|
|
97
|
+
Configure the module with your API key and default context.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface SavvagentConfig {
|
|
101
|
+
config: FlagClientConfig;
|
|
102
|
+
defaultContext?: DefaultFlagContext;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface FlagClientConfig {
|
|
106
|
+
/** SDK API key (starts with sdk_) */
|
|
107
|
+
apiKey: string;
|
|
108
|
+
/** Application ID for application-scoped flags */
|
|
109
|
+
applicationId?: string;
|
|
110
|
+
/** Base URL for the Savvagent API */
|
|
111
|
+
baseUrl?: string;
|
|
112
|
+
/** Enable real-time flag updates via SSE (default: true) */
|
|
113
|
+
enableRealtime?: boolean;
|
|
114
|
+
/** Cache TTL in milliseconds (default: 60000) */
|
|
115
|
+
cacheTtl?: number;
|
|
116
|
+
/** Enable telemetry tracking (default: true) */
|
|
117
|
+
enableTelemetry?: boolean;
|
|
118
|
+
/** Default flag values when evaluation fails */
|
|
119
|
+
defaults?: Record<string, boolean>;
|
|
120
|
+
/** Custom error handler */
|
|
121
|
+
onError?: (error: Error) => void;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface DefaultFlagContext {
|
|
125
|
+
applicationId?: string;
|
|
126
|
+
environment?: string;
|
|
127
|
+
organizationId?: string;
|
|
128
|
+
userId?: string;
|
|
129
|
+
anonymousId?: string;
|
|
130
|
+
sessionId?: string;
|
|
131
|
+
language?: string;
|
|
132
|
+
attributes?: Record<string, any>;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `SavvagentService`
|
|
137
|
+
|
|
138
|
+
Injectable service that provides all feature flag functionality.
|
|
139
|
+
|
|
140
|
+
#### Properties
|
|
141
|
+
|
|
142
|
+
- `ready$: Observable<boolean>` - Observable that emits true when the client is ready
|
|
143
|
+
- `isReady: boolean` - Check if the client is ready synchronously
|
|
144
|
+
- `flagClient: FlagClient | null` - Access the underlying FlagClient for advanced use cases
|
|
145
|
+
|
|
146
|
+
#### `flag$(flagKey, options)`
|
|
147
|
+
|
|
148
|
+
Get a reactive Observable for a feature flag with automatic updates.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface FlagOptions {
|
|
152
|
+
/** Context for flag evaluation (user_id, attributes, etc.) */
|
|
153
|
+
context?: FlagContext;
|
|
154
|
+
/** Default value to use while loading or on error */
|
|
155
|
+
defaultValue?: boolean;
|
|
156
|
+
/** Enable real-time updates for this flag (default: true) */
|
|
157
|
+
realtime?: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface FlagObservableResult {
|
|
161
|
+
/** Current flag value */
|
|
162
|
+
value: boolean;
|
|
163
|
+
/** Whether the flag is currently being evaluated */
|
|
164
|
+
loading: boolean;
|
|
165
|
+
/** Error if evaluation failed */
|
|
166
|
+
error: Error | null;
|
|
167
|
+
/** Detailed evaluation result */
|
|
168
|
+
result: FlagEvaluationResult | null;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Example:**
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
@Component({
|
|
176
|
+
template: `
|
|
177
|
+
<ng-container *ngIf="betaFeature$ | async as flag">
|
|
178
|
+
<div *ngIf="flag.loading">Loading...</div>
|
|
179
|
+
<div *ngIf="flag.error">Error: {{ flag.error.message }}</div>
|
|
180
|
+
<app-beta *ngIf="flag.value"></app-beta>
|
|
181
|
+
<app-standard *ngIf="!flag.value && !flag.loading"></app-standard>
|
|
182
|
+
</ng-container>
|
|
183
|
+
`
|
|
184
|
+
})
|
|
185
|
+
export class MyComponent {
|
|
186
|
+
betaFeature$ = this.savvagent.flag$('beta-feature', {
|
|
187
|
+
context: {
|
|
188
|
+
user_id: this.userId,
|
|
189
|
+
attributes: { plan: 'pro' }
|
|
190
|
+
},
|
|
191
|
+
defaultValue: false,
|
|
192
|
+
realtime: true
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
constructor(private savvagent: SavvagentService) {}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `flagValue$(flagKey, options)`
|
|
200
|
+
|
|
201
|
+
Get just the boolean value as an Observable. Useful when you don't need loading/error states.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
@Component({
|
|
205
|
+
template: `
|
|
206
|
+
<button *ngIf="isFeatureEnabled$ | async">New Button</button>
|
|
207
|
+
`
|
|
208
|
+
})
|
|
209
|
+
export class SimpleComponent {
|
|
210
|
+
isFeatureEnabled$ = this.savvagent.flagValue$('my-feature');
|
|
211
|
+
|
|
212
|
+
constructor(private savvagent: SavvagentService) {}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### `evaluate(flagKey, context)`
|
|
217
|
+
|
|
218
|
+
Evaluate a feature flag once (non-reactive).
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
async checkFeature() {
|
|
222
|
+
const result = await this.savvagent.evaluate('new-feature');
|
|
223
|
+
console.log(result.value, result.reason);
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### `isEnabled(flagKey, context)`
|
|
228
|
+
|
|
229
|
+
Simple boolean check if a flag is enabled.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
async doSomething() {
|
|
233
|
+
if (await this.savvagent.isEnabled('feature-flag')) {
|
|
234
|
+
// Feature is enabled
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `withFlag(flagKey, callback, context)`
|
|
240
|
+
|
|
241
|
+
Execute code conditionally based on flag value.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
async trackPageView() {
|
|
245
|
+
await this.savvagent.withFlag('analytics-enabled', async () => {
|
|
246
|
+
await this.analytics.track('page_view');
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### `trackError(flagKey, error, context)`
|
|
252
|
+
|
|
253
|
+
Track errors with flag context for AI-powered analysis.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
handleError(error: Error) {
|
|
257
|
+
this.savvagent.trackError('new-payment-flow', error);
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### User Management
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Set user ID for logged-in users
|
|
265
|
+
setUserId(userId: string | null): void;
|
|
266
|
+
getUserId(): string | null;
|
|
267
|
+
|
|
268
|
+
// Anonymous ID management
|
|
269
|
+
getAnonymousId(): string | null;
|
|
270
|
+
setAnonymousId(id: string): void;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### Local Overrides
|
|
274
|
+
|
|
275
|
+
For development and testing:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Set override (takes precedence over server values)
|
|
279
|
+
setOverride(flagKey: string, value: boolean): void;
|
|
280
|
+
|
|
281
|
+
// Clear overrides
|
|
282
|
+
clearOverride(flagKey: string): void;
|
|
283
|
+
clearAllOverrides(): void;
|
|
284
|
+
|
|
285
|
+
// Check overrides
|
|
286
|
+
hasOverride(flagKey: string): boolean;
|
|
287
|
+
getOverride(flagKey: string): boolean | undefined;
|
|
288
|
+
getOverrides(): Record<string, boolean>;
|
|
289
|
+
|
|
290
|
+
// Set multiple overrides
|
|
291
|
+
setOverrides(overrides: Record<string, boolean>): void;
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Flag Discovery
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Get all flags (returns Observable)
|
|
298
|
+
getAllFlags$(environment?: string): Observable<FlagDefinition[]>;
|
|
299
|
+
|
|
300
|
+
// Get all flags (Promise-based)
|
|
301
|
+
getAllFlags(environment?: string): Promise<FlagDefinition[]>;
|
|
302
|
+
|
|
303
|
+
// Get enterprise-scoped flags only
|
|
304
|
+
getEnterpriseFlags(environment?: string): Promise<FlagDefinition[]>;
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Cache & Connection
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
clearCache(): void;
|
|
311
|
+
isRealtimeConnected(): boolean;
|
|
312
|
+
close(): void;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Advanced Examples
|
|
316
|
+
|
|
317
|
+
### User Targeting
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
@Component({...})
|
|
321
|
+
export class UserFeatureComponent implements OnInit {
|
|
322
|
+
premiumFeature$!: Observable<FlagObservableResult>;
|
|
323
|
+
|
|
324
|
+
constructor(
|
|
325
|
+
private savvagent: SavvagentService,
|
|
326
|
+
private auth: AuthService
|
|
327
|
+
) {}
|
|
328
|
+
|
|
329
|
+
ngOnInit() {
|
|
330
|
+
this.premiumFeature$ = this.savvagent.flag$('premium-features', {
|
|
331
|
+
context: {
|
|
332
|
+
user_id: this.auth.userId,
|
|
333
|
+
attributes: {
|
|
334
|
+
plan: this.auth.userPlan,
|
|
335
|
+
signupDate: this.auth.signupDate
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Dynamic Initialization
|
|
344
|
+
|
|
345
|
+
If you need to initialize the service after getting user data:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
@Component({...})
|
|
349
|
+
export class AppComponent implements OnInit {
|
|
350
|
+
constructor(
|
|
351
|
+
private savvagent: SavvagentService,
|
|
352
|
+
private auth: AuthService
|
|
353
|
+
) {}
|
|
354
|
+
|
|
355
|
+
ngOnInit() {
|
|
356
|
+
// Wait for auth, then initialize
|
|
357
|
+
this.auth.user$.pipe(take(1)).subscribe(user => {
|
|
358
|
+
this.savvagent.initialize({
|
|
359
|
+
config: {
|
|
360
|
+
apiKey: environment.savvagentApiKey
|
|
361
|
+
},
|
|
362
|
+
defaultContext: {
|
|
363
|
+
userId: user?.id,
|
|
364
|
+
environment: environment.name
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Error Tracking
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
@Component({...})
|
|
376
|
+
export class PaymentComponent {
|
|
377
|
+
constructor(private savvagent: SavvagentService) {}
|
|
378
|
+
|
|
379
|
+
async processPayment() {
|
|
380
|
+
try {
|
|
381
|
+
const result = await this.paymentService.process();
|
|
382
|
+
return result;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
// Error is correlated with flag changes
|
|
385
|
+
this.savvagent.trackError('new-payment-flow', error as Error);
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### A/B Testing
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
@Component({
|
|
396
|
+
template: `
|
|
397
|
+
<app-checkout-a *ngIf="!(variantB$ | async)"></app-checkout-a>
|
|
398
|
+
<app-checkout-b *ngIf="variantB$ | async"></app-checkout-b>
|
|
399
|
+
`
|
|
400
|
+
})
|
|
401
|
+
export class ABTestComponent {
|
|
402
|
+
variantB$ = this.savvagent.flagValue$('checkout-variant-b', {
|
|
403
|
+
context: {
|
|
404
|
+
user_id: this.userId // Consistent assignment per user
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
constructor(private savvagent: SavvagentService) {}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Development Override Panel
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
@Component({
|
|
416
|
+
selector: 'app-flag-overrides',
|
|
417
|
+
template: `
|
|
418
|
+
<div *ngFor="let flag of flags$ | async">
|
|
419
|
+
<label>
|
|
420
|
+
<input
|
|
421
|
+
type="checkbox"
|
|
422
|
+
[checked]="savvagent.getOverride(flag.key) ?? flag.enabled"
|
|
423
|
+
(change)="toggleOverride(flag.key, $event)"
|
|
424
|
+
/>
|
|
425
|
+
{{ flag.key }}
|
|
426
|
+
</label>
|
|
427
|
+
<button (click)="clearOverride(flag.key)">Reset</button>
|
|
428
|
+
</div>
|
|
429
|
+
`
|
|
430
|
+
})
|
|
431
|
+
export class FlagOverridesComponent implements OnInit {
|
|
432
|
+
flags$ = this.savvagent.getAllFlags$('development');
|
|
433
|
+
|
|
434
|
+
constructor(public savvagent: SavvagentService) {}
|
|
435
|
+
|
|
436
|
+
toggleOverride(flagKey: string, event: Event) {
|
|
437
|
+
const checked = (event.target as HTMLInputElement).checked;
|
|
438
|
+
this.savvagent.setOverride(flagKey, checked);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
clearOverride(flagKey: string) {
|
|
442
|
+
this.savvagent.clearOverride(flagKey);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## TypeScript Support
|
|
448
|
+
|
|
449
|
+
This package is written in TypeScript and provides full type definitions.
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
import type {
|
|
453
|
+
FlagClientConfig,
|
|
454
|
+
FlagContext,
|
|
455
|
+
FlagEvaluationResult,
|
|
456
|
+
FlagDefinition,
|
|
457
|
+
SavvagentConfig,
|
|
458
|
+
DefaultFlagContext,
|
|
459
|
+
FlagObservableResult,
|
|
460
|
+
FlagOptions,
|
|
461
|
+
} from '@savvagent/angular';
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Best Practices
|
|
465
|
+
|
|
466
|
+
1. **Import SavvagentModule.forRoot() in your root module** to ensure a single instance of the service.
|
|
467
|
+
|
|
468
|
+
2. **Use the `defaultValue` option** to provide a safe fallback while flags are loading.
|
|
469
|
+
|
|
470
|
+
3. **Enable real-time updates** for flags that change frequently or require immediate propagation.
|
|
471
|
+
|
|
472
|
+
4. **Track errors** in new features to leverage Savvagent's AI-powered error correlation.
|
|
473
|
+
|
|
474
|
+
5. **Use user context** for targeted rollouts based on user attributes, location, or behavior.
|
|
475
|
+
|
|
476
|
+
6. **Handle loading states** gracefully using the async pipe and conditional rendering.
|
|
477
|
+
|
|
478
|
+
7. **Use `flagValue$`** when you only need the boolean value without loading/error states.
|
|
479
|
+
|
|
480
|
+
8. **Clean up subscriptions** - the service handles cleanup automatically on destroy, but use `takeUntil` or similar patterns in components for long-lived subscriptions.
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
MIT
|