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:

  1. How to enable/disable an existing feature.
  2. How to add a new feature for flagging.
  3. 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:

  1. Update the features Configuration: Add the feature to src/app/constants/feature-options.ts:
  export const features: FeatureDefinitions = {
+   newFeature: {
+     default: false,
+     managed: {
+       free: false,
+       pro: true,
+       enterprise: true,
+     },
+     selfHosted: true,
+   },
  };
  1. 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.',
+   },
  };
  1. 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

Initializing

We're just getting everything ready for you. This shouldn't take a moment...