AAE-36660 Enhance feature flags service with Signal support

This commit is contained in:
Wojciech Duda
2025-10-30 15:35:15 +01:00
parent 1061e762f8
commit 66b4983a75
5 changed files with 60 additions and 4 deletions

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { InjectionToken } from '@angular/core';
import { InjectionToken, Signal } from '@angular/core';
import { Observable } from 'rxjs';
export const FeaturesServiceConfigToken = new InjectionToken<any>('FeatureServiceConfigToken');
@@ -53,8 +53,11 @@ export interface FlagSet {
export interface IFeaturesService<T = FlagChangeset> {
init(): Observable<T>;
isOn(key: string): Signal<boolean>;
isOff(key: string): Signal<boolean>;
isOn$(key: string): Observable<boolean>;
isOff$(key: string): Observable<boolean>;
getFlags(): Signal<T>;
getFlags$(): Observable<T>;
}

View File

@@ -17,6 +17,7 @@
import { of } from 'rxjs';
import { FeaturesServiceToken, FlagChangeset, IFeaturesService } from '../interfaces/features.interface';
import { signal } from '@angular/core';
export interface MockFeatureFlags {
[key: string]: boolean;
@@ -34,6 +35,14 @@ const assertFeatureFlag = (flagChangeset: FlagChangeset, key: string): void => {
const mockFeaturesService = (flagChangeset: FlagChangeset): IFeaturesService => ({
init: () => of(flagChangeset),
isOn: (key) => {
assertFeatureFlag(flagChangeset, key);
return signal(flagChangeset[key].current);
},
isOff: (key) => {
assertFeatureFlag(flagChangeset, key);
return signal(!flagChangeset[key].current);
},
isOn$: (key) => {
assertFeatureFlag(flagChangeset, key);
return of(flagChangeset[key].current);
@@ -42,6 +51,7 @@ const mockFeaturesService = (flagChangeset: FlagChangeset): IFeaturesService =>
assertFeatureFlag(flagChangeset, key);
return of(!flagChangeset[key].current);
},
getFlags: () => signal(flagChangeset),
getFlags$: () => of(flagChangeset)
});

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Injectable, inject } from '@angular/core';
import { Injectable, inject, Signal } from '@angular/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
import {
@@ -28,6 +28,7 @@ import {
WritableFeaturesServiceConfig,
FlagSet
} from '../interfaces/features.interface';
import { toSignal } from '@angular/core/rxjs-interop';
@Injectable()
export class DebugFeaturesService implements IDebugFeaturesService {
@@ -54,6 +55,14 @@ export class DebugFeaturesService implements IDebugFeaturesService {
});
}
isOn(key: string): Signal<boolean> {
return toSignal(this.isOn$(key));
}
isOff(key: string): Signal<boolean> {
return toSignal(this.isOff$(key));
}
isOn$(key: string): Observable<boolean> {
return this.isInDebugMode$.pipe(
switchMap((isInDebugMode) => (isInDebugMode ? this.writableFeaturesService : this.overriddenFeaturesService).isOn$(key))
@@ -66,6 +75,15 @@ export class DebugFeaturesService implements IDebugFeaturesService {
);
}
/**
* Gets the flags as a signal.
*
* @returns the signal that emits the flag changeset.
*/
getFlags(): Signal<FlagChangeset> {
return toSignal(this.getFlags$());
}
/**
* Gets the flags as an observable.
*

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Injectable, signal, Signal } from '@angular/core';
import { Observable, of } from 'rxjs';
import { IFeaturesService, FlagChangeset } from '../interfaces/features.interface';
@@ -25,6 +25,14 @@ export class DummyFeaturesService implements IFeaturesService {
return of();
}
isOn(_key: string): Signal<boolean> {
return signal(false);
}
isOff(_key: string): Signal<boolean> {
return signal(true);
}
isOn$(): Observable<boolean> {
return of(false);
}
@@ -33,6 +41,10 @@ export class DummyFeaturesService implements IFeaturesService {
return of(true);
}
getFlags(): Signal<FlagChangeset> {
return signal({});
}
getFlags$(): Observable<FlagChangeset> {
return of({});
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Injectable, inject } from '@angular/core';
import { Injectable, inject, Signal } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import {
@@ -28,6 +28,7 @@ import {
WritableFeaturesServiceConfig
} from '../interfaces/features.interface';
import { FlagSetParser } from './flagset.parser';
import { toSignal } from '@angular/core/rxjs-interop';
@Injectable({ providedIn: 'root' })
export class StorageFeaturesService implements IFeaturesService, IWritableFeaturesService {
@@ -60,6 +61,14 @@ export class StorageFeaturesService implements IFeaturesService, IWritableFeatur
return of(initialFlagChangeSet);
}
isOn(key: string): Signal<boolean> {
return toSignal(this.isOn$(key));
}
isOff(key: string): Signal<boolean> {
return toSignal(this.isOff$(key));
}
isOn$(key: string): Observable<boolean> {
return this.flags$.pipe(map((flags) => !!flags[key]?.current));
}
@@ -68,6 +77,10 @@ export class StorageFeaturesService implements IFeaturesService, IWritableFeatur
return this.flags$.pipe(map((flags) => !flags[key]?.current));
}
getFlags(): Signal<WritableFlagChangeset> {
return toSignal(this.flags$);
}
getFlags$(): Observable<WritableFlagChangeset> {
return this.flags$;
}