Skip to content

Commit ed16cc5

Browse files
feat: add feature flag service for angular (#1247)
<!-- Please use this template for your pull request. --> <!-- Please use the sections that you need and delete other sections --> NOTE: This is still a draft for discussion! ## This PR <!-- add the description of the PR here --> Adds a service for evaluating flags in angular services. It enables application authors to evaluate flags directly as observables or signal. ~~I am not 100% sure about returning signals or leaving the `toSignal` to the application autors. As far as I understand, when used like the following, subscriptions for `toSignal` will still be cleaned up correctly, as the injection context will be captured correctly in that case:~~ ```js @component({ template: ` <div data-testid="value">{{ thumbs()?.value ? '👍' : '👎' }}</div> <div data-testid="reason">reason: {{ thumbs()?.reason }}</div> `, standalone: true, }) class TestSignalComponent { private flagService = inject(FeatureFlagService); thumbs = this.flagService.getBooleanDetailsSignal(FLAG_KEY, false); } ``` Edit: We decided to leave the `toSignal` to the app authors. @juanparadox please leave feedback. I still have to clean up the tests and the code in general, if we decide this is the design we opt for. --------- Signed-off-by: Lukas Reining <[email protected]>
1 parent 7d68d32 commit ed16cc5

File tree

7 files changed

+792
-8
lines changed

7 files changed

+792
-8
lines changed

packages/angular/projects/angular-sdk/README.md

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ No `else`, `initializing`, or `reconciling` templates are required in this case.
144144

145145
#### How to use
146146

147+
The library provides two main ways to work with feature flags:
148+
149+
1. **Structural Directives** - For template-based conditional rendering
150+
2. **FeatureFlagService** - For programmatic access with Observables
151+
152+
##### Structural Directives
153+
147154
The library provides four primary directives for feature flags, `booleanFeatureFlag`,
148155
`numberFeatureFlag`, `stringFeatureFlag` and `objectFeatureFlag`.
149156

@@ -167,7 +174,7 @@ The template referenced in `initializing` and `reconciling` will be rendered if
167174
corresponding states.
168175
This parameter is _optional_, if omitted, the `then` and `else` templates will be rendered according to the flag value.
169176

170-
##### Boolean Feature Flag
177+
###### Boolean Feature Flag
171178

172179
```html
173180
<div
@@ -185,7 +192,7 @@ This parameter is _optional_, if omitted, the `then` and `else` templates will b
185192
</ng-template>
186193
```
187194

188-
##### Number Feature Flag
195+
###### Number Feature Flag
189196

190197
```html
191198
<div
@@ -203,7 +210,7 @@ This parameter is _optional_, if omitted, the `then` and `else` templates will b
203210
</ng-template>
204211
```
205212

206-
##### String Feature Flag
213+
###### String Feature Flag
207214

208215
```html
209216
<div
@@ -221,7 +228,7 @@ This parameter is _optional_, if omitted, the `then` and `else` templates will b
221228
</ng-template>
222229
```
223230

224-
##### Object Feature Flag
231+
###### Object Feature Flag
225232

226233
```html
227234
<div
@@ -239,7 +246,7 @@ This parameter is _optional_, if omitted, the `then` and `else` templates will b
239246
</ng-template>
240247
```
241248

242-
##### Opting-out of automatic re-rendering
249+
###### Opting-out of automatic re-rendering
243250

244251
By default, the directive re-renders when the flag value changes or the context changes.
245252

@@ -251,7 +258,7 @@ In cases, this is not desired, re-rendering can be disabled for both events:
251258
</div>
252259
```
253260

254-
##### Consuming the evaluation details
261+
###### Consuming the evaluation details
255262

256263
The `evaluation details` can be used when rendering the templates.
257264
The directives [`$implicit`](https://angular.dev/guide/directives/structural-directives#structural-directive-shorthand)
@@ -282,6 +289,86 @@ This can be used to just render the flag value or details without conditional re
282289
</div>
283290
```
284291

292+
##### FeatureFlagService
293+
294+
The `FeatureFlagService` provides programmatic access to feature flags through reactive patterns. All methods return
295+
Observables that automatically emit new values when flag configurations or evaluation context changes.
296+
297+
###### Using with Observables
298+
299+
```typescript
300+
import { Component, inject } from '@angular/core';
301+
import { AsyncPipe } from '@angular/common';
302+
import { FeatureFlagService } from '@openfeature/angular-sdk';
303+
304+
@Component({
305+
selector: 'my-component',
306+
standalone: true,
307+
imports: [AsyncPipe],
308+
template: `
309+
<div *ngIf="(isFeatureEnabled$ | async)?.value">
310+
Feature is enabled! Reason: {{ (isFeatureEnabled$ | async)?.reason }}
311+
</div>
312+
<div>Theme: {{ (currentTheme$ | async)?.value }}</div>
313+
<div>Max items: {{ (maxItems$ | async)?.value }}</div>
314+
`
315+
})
316+
export class MyComponent {
317+
private flagService = inject(FeatureFlagService);
318+
319+
// Boolean flag
320+
isFeatureEnabled$ = this.flagService.getBooleanDetails('my-feature', false);
321+
322+
// String flag
323+
currentTheme$ = this.flagService.getStringDetails('theme', 'light');
324+
325+
// Number flag
326+
maxItems$ = this.flagService.getNumberDetails('max-items', 10);
327+
328+
// Object flag with type safety
329+
config$ = this.flagService.getObjectDetails<{ timeout: number }>('api-config', { timeout: 5000 });
330+
}
331+
```
332+
333+
###### Using with Angular Signals
334+
335+
You can convert any Observable from the service to an Angular Signal using `toSignal()`:
336+
337+
```typescript
338+
import { Component, inject } from '@angular/core';
339+
import { toSignal } from '@angular/core/rxjs-interop';
340+
import { FeatureFlagService } from '@openfeature/angular-sdk';
341+
342+
@Component({
343+
selector: 'my-component',
344+
standalone: true,
345+
template: `
346+
<div *ngIf="isFeatureEnabled()?.value">
347+
Feature is enabled! Reason: {{ isFeatureEnabled()?.reason }}
348+
</div>
349+
<div>Theme: {{ currentTheme()?.value }}</div>
350+
`
351+
})
352+
export class MyComponent {
353+
private flagService = inject(FeatureFlagService);
354+
355+
// Convert Observables to Signals
356+
isFeatureEnabled = toSignal(this.flagService.getBooleanDetails('my-feature', false));
357+
currentTheme = toSignal(this.flagService.getStringDetails('theme', 'light'));
358+
}
359+
```
360+
361+
###### Service Options
362+
363+
The service methods accept the [same options as the directives](#opting-out-of-automatic-re-rendering):
364+
365+
```typescript
366+
const flag$ = this.flagService.getBooleanDetails('my-flag', false, 'my-domain', {
367+
updateOnConfigurationChanged: false, // default: true
368+
updateOnContextChanged: false, // default: true
369+
});
370+
```
371+
285372
##### Setting evaluation context
286373

287374
To set the initial evaluation context, you can add the `context` parameter to the `OpenFeatureModule` configuration.

0 commit comments

Comments
 (0)