From 5cb9dbe53193fa6ac5bb778c3c1eefde1de5490e Mon Sep 17 00:00:00 2001 From: backtrack5r3 Date: Mon, 23 Feb 2026 15:14:29 +0100 Subject: [PATCH 1/3] feat(angular): add convenience value method alongside details method in generated service Signed-off-by: backtrack5r3 --- internal/cmd/testdata/success_angular.golden | 91 +++++++++++++++++ internal/generators/angular/angular.tmpl | 19 ++++ .../angular-integration/specs/service.spec.ts | 99 +++++++++++++++++++ 3 files changed, 209 insertions(+) diff --git a/internal/cmd/testdata/success_angular.golden b/internal/cmd/testdata/success_angular.golden index b0deeed..086d537 100644 --- a/internal/cmd/testdata/success_angular.golden +++ b/internal/cmd/testdata/success_angular.golden @@ -27,6 +27,7 @@ import { FeatureFlagService, JsonValue, } from '@openfeature/angular-sdk'; +import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; // ============================================================================ @@ -131,6 +132,24 @@ export class GeneratedFeatureFlagService { ); } + /** + * Get the value of the `discountPercentage` flag. + * + * Discount percentage applied to purchases. + * + * @param domain - Optional domain for flag evaluation (scopes the flag to a specific provider). + * @param options - Optional configuration for the flag evaluation. + * @returns An Observable that emits the flag value whenever it changes. + */ + getDiscountPercentage( + domain?: string, + options?: AngularFlagEvaluationOptions + ): Observable { + return this.getDiscountPercentageDetails(domain, options).pipe( + map((details) => details.value) + ); + } + /** * Get evaluation details for the `enableFeatureA` flag. * @@ -160,6 +179,24 @@ export class GeneratedFeatureFlagService { ); } + /** + * Get the value of the `enableFeatureA` flag. + * + * Controls whether Feature A is enabled. + * + * @param domain - Optional domain for flag evaluation (scopes the flag to a specific provider). + * @param options - Optional configuration for the flag evaluation. + * @returns An Observable that emits the flag value whenever it changes. + */ + getEnableFeatureA( + domain?: string, + options?: AngularFlagEvaluationOptions + ): Observable { + return this.getEnableFeatureADetails(domain, options).pipe( + map((details) => details.value) + ); + } + /** * Get evaluation details for the `greetingMessage` flag. * @@ -189,6 +226,24 @@ export class GeneratedFeatureFlagService { ); } + /** + * Get the value of the `greetingMessage` flag. + * + * The message to use for greeting users. + * + * @param domain - Optional domain for flag evaluation (scopes the flag to a specific provider). + * @param options - Optional configuration for the flag evaluation. + * @returns An Observable that emits the flag value whenever it changes. + */ + getGreetingMessage( + domain?: string, + options?: AngularFlagEvaluationOptions + ): Observable { + return this.getGreetingMessageDetails(domain, options).pipe( + map((details) => details.value) + ); + } + /** * Get evaluation details for the `themeCustomization` flag. * @@ -218,6 +273,24 @@ export class GeneratedFeatureFlagService { ); } + /** + * Get the value of the `themeCustomization` flag. + * + * Allows customization of theme colors. + * + * @param domain - Optional domain for flag evaluation (scopes the flag to a specific provider). + * @param options - Optional configuration for the flag evaluation. + * @returns An Observable that emits the flag value whenever it changes. + */ + getThemeCustomization( + domain?: string, + options?: AngularFlagEvaluationOptions + ): Observable { + return this.getThemeCustomizationDetails(domain, options).pipe( + map((details) => details.value) + ); + } + /** * Get evaluation details for the `usernameMaxLength` flag. * @@ -247,6 +320,24 @@ export class GeneratedFeatureFlagService { ); } + /** + * Get the value of the `usernameMaxLength` flag. + * + * Maximum allowed length for usernames. + * + * @param domain - Optional domain for flag evaluation (scopes the flag to a specific provider). + * @param options - Optional configuration for the flag evaluation. + * @returns An Observable that emits the flag value whenever it changes. + */ + getUsernameMaxLength( + domain?: string, + options?: AngularFlagEvaluationOptions + ): Observable { + return this.getUsernameMaxLengthDetails(domain, options).pipe( + map((details) => details.value) + ); + } + } // ============================================================================ diff --git a/internal/generators/angular/angular.tmpl b/internal/generators/angular/angular.tmpl index f0766ff..0d1cc66 100644 --- a/internal/generators/angular/angular.tmpl +++ b/internal/generators/angular/angular.tmpl @@ -27,6 +27,7 @@ import { FeatureFlagService, JsonValue, } from '@openfeature/angular-sdk'; +import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; // ============================================================================ @@ -108,6 +109,24 @@ export class GeneratedFeatureFlagService { } ); } + + /** + * Get the value of the `{{ .Key }}` flag. + * + * {{ if .Description }}{{ .Description }}{{ else }}Feature flag.{{ end }} + * + * @param domain - Optional domain for flag evaluation (scopes the flag to a specific provider). + * @param options - Optional configuration for the flag evaluation. + * @returns An Observable that emits the flag value whenever it changes. + */ + get{{ .Key | ToPascal }}( + domain?: string, + options?: AngularFlagEvaluationOptions + ): Observable<{{ if eq (.Type | OpenFeatureType) "object" }}JsonValue{{ else }}{{ .Type | OpenFeatureType }}{{ end }}> { + return this.get{{ .Key | ToPascal }}Details(domain, options).pipe( + map((details) => details.value) + ); + } {{ end }} } diff --git a/test/angular-integration/specs/service.spec.ts b/test/angular-integration/specs/service.spec.ts index 6dc4a8a..11c9a58 100644 --- a/test/angular-integration/specs/service.spec.ts +++ b/test/angular-integration/specs/service.spec.ts @@ -211,4 +211,103 @@ describe("GeneratedFeatureFlagService", () => { }); }); }); + + describe("getEnableFeatureA (convenience)", () => { + it("should return boolean value directly", async () => { + const value = await firstValueFrom(service.getEnableFeatureA(domain)); + + expect(typeof value).toBe("boolean"); + expect(value).toBe(true); + }); + + it("should return default value when flag is not configured", async () => { + const emptyDomain = uuid(); + const emptyProvider = new InMemoryProvider({}); + await OpenFeature.setProviderAndWait(emptyDomain, emptyProvider); + + const value = await firstValueFrom( + service.getEnableFeatureA(emptyDomain), + ); + + expect(value).toBe(false); + }); + }); + + describe("getGreetingMessage (convenience)", () => { + it("should return string value directly", async () => { + const value = await firstValueFrom(service.getGreetingMessage(domain)); + + expect(typeof value).toBe("string"); + expect(value).toBe("Hello from provider!"); + }); + + it("should return default value when flag is not configured", async () => { + const emptyDomain = uuid(); + const emptyProvider = new InMemoryProvider({}); + await OpenFeature.setProviderAndWait(emptyDomain, emptyProvider); + + const value = await firstValueFrom( + service.getGreetingMessage(emptyDomain), + ); + + expect(value).toBe("Hello there!"); + }); + }); + + describe("getDiscountPercentage (convenience)", () => { + it("should return number value directly", async () => { + const value = await firstValueFrom(service.getDiscountPercentage(domain)); + + expect(typeof value).toBe("number"); + expect(value).toBe(0.25); + }); + + it("should return default value when flag is not configured", async () => { + const emptyDomain = uuid(); + const emptyProvider = new InMemoryProvider({}); + await OpenFeature.setProviderAndWait(emptyDomain, emptyProvider); + + const value = await firstValueFrom( + service.getDiscountPercentage(emptyDomain), + ); + + expect(value).toBe(0.15); + }); + }); + + describe("getUsernameMaxLength (convenience)", () => { + it("should return number value directly", async () => { + const value = await firstValueFrom(service.getUsernameMaxLength(domain)); + + expect(typeof value).toBe("number"); + expect(value).toBe(100); + }); + }); + + describe("getThemeCustomization (convenience)", () => { + it("should return object value directly", async () => { + const value = await firstValueFrom(service.getThemeCustomization(domain)); + + expect(typeof value).toBe("object"); + expect(value).toEqual({ + primaryColor: "#ff0000", + secondaryColor: "#00ff00", + }); + }); + + it("should return default value when flag is not configured", async () => { + const emptyDomain = uuid(); + const emptyProvider = new InMemoryProvider({}); + await OpenFeature.setProviderAndWait(emptyDomain, emptyProvider); + + const value = await firstValueFrom( + service.getThemeCustomization(emptyDomain), + ); + + expect(value).toEqual({ + primaryColor: "#007bff", + secondaryColor: "#6c757d", + }); + }); + }); }); From 61c9536dc3dd4ccfbd472df8c0400af76a5966ef Mon Sep 17 00:00:00 2001 From: backtrack5r3 Date: Mon, 23 Feb 2026 15:24:16 +0100 Subject: [PATCH 2/3] refactor(angular): import map from rxjs correctly The rxjs docs gives best practices : https://rxjs.dev/guide/importing#new-in-rxjs-v720 Signed-off-by: backtrack5r3 --- internal/cmd/testdata/success_angular.golden | 2 +- internal/generators/angular/angular.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cmd/testdata/success_angular.golden b/internal/cmd/testdata/success_angular.golden index 086d537..e8cbf6f 100644 --- a/internal/cmd/testdata/success_angular.golden +++ b/internal/cmd/testdata/success_angular.golden @@ -27,7 +27,7 @@ import { FeatureFlagService, JsonValue, } from '@openfeature/angular-sdk'; -import { map } from 'rxjs/operators'; +import { map } from 'rxjs'; import { Observable } from 'rxjs'; // ============================================================================ diff --git a/internal/generators/angular/angular.tmpl b/internal/generators/angular/angular.tmpl index 0d1cc66..8f42c81 100644 --- a/internal/generators/angular/angular.tmpl +++ b/internal/generators/angular/angular.tmpl @@ -27,7 +27,7 @@ import { FeatureFlagService, JsonValue, } from '@openfeature/angular-sdk'; -import { map } from 'rxjs/operators'; +import { map } from 'rxjs'; import { Observable } from 'rxjs'; // ============================================================================ From abd1ee7c92e711f2a85e59471e788aa2c0bf4765 Mon Sep 17 00:00:00 2001 From: backtrack5r3 Date: Tue, 24 Feb 2026 16:28:46 +0100 Subject: [PATCH 3/3] refactor: simplify rxjs imports Signed-off-by: backtrack5r3 --- internal/cmd/testdata/success_angular.golden | 3 +-- internal/generators/angular/angular.tmpl | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/cmd/testdata/success_angular.golden b/internal/cmd/testdata/success_angular.golden index e8cbf6f..14acf3d 100644 --- a/internal/cmd/testdata/success_angular.golden +++ b/internal/cmd/testdata/success_angular.golden @@ -27,8 +27,7 @@ import { FeatureFlagService, JsonValue, } from '@openfeature/angular-sdk'; -import { map } from 'rxjs'; -import { Observable } from 'rxjs'; +import { Observable, map } from 'rxjs'; // ============================================================================ // FLAG KEYS diff --git a/internal/generators/angular/angular.tmpl b/internal/generators/angular/angular.tmpl index 8f42c81..9ebdbf1 100644 --- a/internal/generators/angular/angular.tmpl +++ b/internal/generators/angular/angular.tmpl @@ -27,8 +27,7 @@ import { FeatureFlagService, JsonValue, } from '@openfeature/angular-sdk'; -import { map } from 'rxjs'; -import { Observable } from 'rxjs'; +import { Observable, map } from 'rxjs'; // ============================================================================ // FLAG KEYS