Skip to content

Conversation

lukas-reining
Copy link
Member

@lukas-reining lukas-reining commented Sep 15, 2025

NOTE: This is still a draft for discussion!

This PR

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:

@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.

@beeme1mr
Copy link
Member

@lukas-reining, what would it look like if you left the toSignal up to the author? Your proposal looks good to me but it has been years since I've used Angular.

@lukas-reining
Copy link
Member Author

@lukas-reining, what would it look like if you left the toSignal up to the author? Your proposal looks good to me but it has been years since I've used Angular.

@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 = toSignal(this.flagService.getBooleanDetails(FLAG_KEY, false));
}

@beeme1mr it would just be removing the methods for signals and wrapping the observable methods in toSignal as seen above.

@juanparadox
Copy link
Member

@lukas-reining, what would it look like if you left the toSignal up to the author? Your proposal looks good to me but it has been years since I've used Angular.

@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 = toSignal(this.flagService.getBooleanDetails(FLAG_KEY, false));
}

@beeme1mr it would just be removing the methods for signals and wrapping the observable methods in toSignal as seen above.

This would mean that the user can choose to consume the evaluation as an Observable or as a Signal, right? If that is the case, and if it is also the case that Angular wants to continue supporting both Observables and Signals, then I really like the approach of leaving toSignal to the user. This creates more flexibility in how the evaluation results are used/consumed.

@lukas-reining
Copy link
Member Author

This would mean that the user can choose to consume the evaluation as an Observable or as a Signal, right? If that is the case, and if it is also the case that Angular wants to continue supporting both Observables and Signals, then I really like the approach of leaving toSignal to the user. This creates more flexibility in how the evaluation results are used/consumed.

Exactly, I also lean towards only having the observables in the API of the service. Having it in would just be hiding toSignal from the app authors.

@juanparadox
Copy link
Member

This would mean that the user can choose to consume the evaluation as an Observable or as a Signal, right? If that is the case, and if it is also the case that Angular wants to continue supporting both Observables and Signals, then I really like the approach of leaving toSignal to the user. This creates more flexibility in how the evaluation results are used/consumed.

Exactly, I also lean towards only having the observables in the API of the service. Having it in would just be hiding toSignal from the app authors.

Okay cool - I'd lean for leaving the toSignal out then 👍

Signed-off-by: Lukas Reining <[email protected]>
@lukas-reining lukas-reining marked this pull request as ready for review September 24, 2025 14:25
@lukas-reining lukas-reining requested review from a team as code owners September 24, 2025 14:25
@lukas-reining
Copy link
Member Author

Okay great!
This is ready to review now, I tried to make it similar to how React works @toddbaert.
Would be happy about a review @beeme1mr @juanparadox @toddbaert :)

Copy link
Member

@juanparadox juanparadox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it - straightforward and easy to understand how it's used for both Observables and Signals 👍

* @param {FlagValue} other Second value to compare
* @returns {boolean} True if the values are equal
*/
export function isEqual(value: FlagValue, other: FlagValue): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this to core? I could go either way since it's small and is unlikely to change but the same function is also in the React SDK.

// Initial evaluation
updateValue();
return () => {
controller.abort();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will this callback fire? Sorry if it's a basic question, I'm not that familiar with Observables. I just want to make sure we're not prematurely unregistering the handlers.


export interface OpenFeatureConfig {
provider: Provider;
provider?: Provider;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a JSDoc to explain these config options? It would help the developer to understand when to provide a Provider and when it's okay to omit that value. Thanks!

) {
super(flagConfiguration);
if (!delay) {
Object.assign(this, { initialize: undefined });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clever. I know why you did this but future me may not. Could you please add a comment explaining why this was necessary?

Feature is enabled! Reason: {{ (isFeatureEnabled$ | async)?.reason }}
</div>
<div>Theme: {{ (currentTheme$ | async)?.value }}</div>
<div>Max items: {{ (maxItems$ | async)?.value }}</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need the ?? Won't value always be there once the flag been evaluated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants