Feature Flagging
Feature flagging allows you to enable or disable specific features based on the user's environment (managed or self-hosted) and billing plan. This section covers:
- How to enable/disable an existing feature.
- How to add a new feature for flagging.
- How to show a placeholder when a feature is unavailable.
Enabling/Disabling an Existing Feature
To enable or disable a feature, use the FeatureService
and the isFeatureEnabled
method. Here's an example of adding a conditional check for a feature in the navbar:
<a routerLink="/settings" *ngIf="isAuthenticated">
- <button pButton icon="pi pi-wrench" label="Settings"></button>
+ <button
+ pButton
+ icon="pi pi-wrench"
+ label="Settings"
+ *ngIf="isAuthenticated && (settingsEnabled$ | async)"
+ ></button>
</a>
In the corresponding component:
+ import { FeatureService } from '~/app/services/features.service';
export class NavbarComponent {
+ settingsEnabled$ = this.featureService.isFeatureEnabled('accountSettings');
constructor(
+ private featureService: FeatureService
) {}
}
Adding a New Feature for Flagging
To add a new feature:
- Update the
features
Configuration: Add the feature tosrc/app/constants/feature-options.ts
:
export const features: FeatureDefinitions = {
+ newFeature: {
+ default: false,
+ managed: {
+ free: false,
+ pro: true,
+ enterprise: true,
+ },
+ selfHosted: true,
+ },
};
- Add a Description: Update
featureDescriptions
to include a label and description:
export const featureDescriptions: Record<keyof FeatureDefinitions, { label: string; description: string }> = {
+ newFeature: {
+ label: 'New Feature',
+ description: 'This is a description of the new feature.',
+ },
};
- Use the Feature in Components: Use the
FeatureService
to check if the feature is enabled:
newFeatureEnabled$ = this.featureService.isFeatureEnabled('newFeature');
Showing a Placeholder for Unavailable Features
If a feature is unavailable, use the FeatureNotEnabledComponent
to display a message to the user:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FeatureNotEnabledComponent } from '~/app/components/misc/feature-not-enabled.component';
@Component({
standalone: true,
selector: 'app-example-component',
imports: [CommonModule, FeatureNotEnabledComponent],
template: \`
<ng-container *ngIf="featureEnabled$ | async; else featureNotAvailable">
<p>The feature is enabled and functional.</p>
</ng-container>
<ng-template #featureNotAvailable>
<app-feature-not-enabled feature="newFeature"></app-feature-not-enabled>
</ng-template>
\`,
})
export class ExampleComponent {
featureEnabled$ = this.featureService.isFeatureEnabled('newFeature');
constructor(private featureService: FeatureService) {}
}
The FeatureNotEnabledComponent
automatically determines the reason a feature is unavailable and shows a tailored message.
Enabling/Disabling a Feature in TS
It's easier to use Promises instead of Observables when feature flagging something in the Typescript code.
For this, we can use the isFeatureEnabledPromise
method instead.
if (!(await this.featureService.isFeatureEnabledPromise('writePermissions'))) {
this.globalMessagingService.showWarn(
Write Permissions Disabled',
'It\'s not possible to add subdomains on the demo instance.',
);
return;
}
Notes
- Dynamic Feature Updates: Features are resolved reactively based on user plan and environment.
- Default Behavior: Features without specific configuration fallback to the
default
value. - Error Logging: If a feature's value cannot be resolved correctly, an error is logged, and the feature is disabled.
flowchart TD subgraph App_Init EnvService["EnvService.getEnvironmentType"] --> FeatureService BillingService["BillingService.getUserPlan"] --> FeatureService FeatureService --> ResolveFeatures ResolveFeatures --> ActiveFeatures["activeFeatures$ (BehaviorSubject)"] end subgraph Feature_Service ActiveFeatures --> GetFeatureValue["getFeatureValue"] GetFeatureValue --> IsFeatureEnabled["isFeatureEnabled"] IsFeatureEnabled -->|if not boolean| ErrorHandler["ErrorHandlerService.handleError"] IsFeatureEnabled -->|if boolean| FeatureFlagResult["Observable: boolean"] IsFeatureEnabled --> IsFeatureEnabledPromise["isFeatureEnabledPromise (Promise)"] end subgraph Component_Usage FeatureFlagResult --> AsyncPipe["*ngIf (async pipe)"] AsyncPipe -->|false| Placeholder["FeatureNotEnabledComponent"] IsFeatureEnabledPromise --> AwaitCheck["if (!featureEnabled) { ... }"] end subgraph Feature_Config FeatureOptions["feature-options.ts"] --> ResolveFeatures FeatureDescriptions --> Placeholder end style Feature_Service fill:#eef7ff,stroke:#93c5fd style Component_Usage fill:#fef3c7,stroke:#facc15 style App_Init fill:#e7f9ed,stroke:#34d399 style Feature_Config fill:#f3e8ff,stroke:#c084fc