@eriklieben/angular-feature-flags
TypeScript icon, indicating that this package has built-in type declarations

16.0.0 • Public • Published

Angular Feature Flags

Usage

*ifFeatureFlag

Show the control if the feature flag is enabled.

<div *ifFeatureFlag="'feature-one-trial:control'">
  <button>click me (feature-one-trial:control)</button>
</div>

*ifFeatureFlag

Show the control if the set of feature flag is enabled. Specify the operator to define how to perform the condition:

  • AND all the feature flags need to be available to show the control
  • OR if one of the feature flags is available, show the control
<div *ifFeatureFlags="['feature-two-trial:treatment','feature-one-trial:control']; operator: 'AND'">
  <button>click me (IF 'feature-two-trial:treatment','feature-one-trial:control' AND)</button>
</div>

*ifNotFeatureFlag

Show the control if the feature flag is not enabled.

<div *ifNotFeatureFlag="'feature-one-trial:treatment'">
  <button>click me (not in feature-one-trial:treatment)</button>
</div>

*ifNotFeatureFlags

Show the control if the set of feature flag is enabled. Specify the operator to define how to perform the condition:

  • AND all the feature flags need to be available to hide the control
  • OR if one of the feature flags is available, hide the control
<div *ifNotFeatureFlags="['feature-two-trial:treatment','feature-one-trial:control']; operator: 'AND'">
  <button>click me (IF 'feature-two-trial:treatment','feature-one-trial:control' AND)</button>
</div>

featureFlagTrace

When a click event is recieved push it to the monitor observable.

<div *ifFeatureFlag="'feature-one-trial:control'">
  <button (click)="title='clicked'" featureFlagTrace="feature-one-trial:control|ok">click me</button>
    <button (click)="title='clicked'" featureFlagTrace="feature-one-trial:control|fail">don't click me</button>
</div>

Access the feature observable from code

If you need the observable in code, you can either use the featureFlagService.featureFlags$ full observable collection, or get it prefiltered by using featureFlagService.featureOn$ or featureFlagService.featureOff$ to recieve a boolean observable.

import { Component } from '@angular/core';
import { FeatureFlagsService } from '@eriklieben/angular-feature-flags';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-comp-two',
  templateUrl: './comp-two.component.html',
  styleUrls: ['./comp-two.component.scss']
})
export class CompTwoComponent {
  public features$: Observable<Set<string>>;
  public featureOn$: Observable<boolean>;

  constructor(
    featureFlagService: FeatureFlagsService) {
    this.features$ = featureFlagService.featureFlags$;
    this.featureOn$ = featureFlagService.featureOn$('feature-one-trail:control');
  }
}
<ul *ngIf="(features$ | async) as features">
  <li *ngFor="let feature of features">
    {{feature}}
  </li>
</ul>

Perform a one-time check.

import { Component } from '@angular/core';
import { FeatureFlagsService } from '@eriklieben/angular-feature-flags';

@Component({
  selector: 'app-comp-two',
  templateUrl: './comp-two.component.html',
  styleUrls: ['./comp-two.component.scss']
})
export class CompTwoComponent {
  public isEnabled = false;
  public isDisabled = true;

  constructor(featureFlagService: FeatureFlagsService) {
    this.isEnabled = featureFlagService.featureOn('feature-one-trail:control');
    this.isDisabled = featureFlagService.featureOff('feature-one-trail:control');
  }
}

Setup

Install the package npm i @eriklieben/angular-feature-flags -S and configure a FeatureFlagStore to retrieve feature toggles.

In the sample below we assume you recieve the following data from the server when calling /api/getfeaturetoggles:

{
  "featureFlags": {
      "feature-one-trial" : "control",
      "feature-two-trial" : "treatment",
      "feature-source": "app" // only here for demonstration
  }
}

Configure your store to map the data to a list of strings for your features.

@Injectable({
  providedIn: 'root',
})
export class AppFeatureStore implements FeatureFlagStore {
  featureFlags$: Observable<string[]>;

  constructor(httpClient: HttpClient) {
    this.featureFlags$ = httpClient.get<FeatureToggleData>('/api/getfeaturetoggles')
    .pipe(map((val) => {
      const flags: string[] = [];
      const featureFlags = Object.keys(val.featureFlags);
      for(let flag of featureFlags) {
        flags.push(`${flag}:${val.featureFlags[flag]}`);
      }
      return flags;
    }),
    shareReplay(1));
  }
}
interface FeatureToggleData {
  featureFlags: {[key: string]: any};
}

Add the feature store implementation to your providers sections:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpClientModule,
    BrowserModule,
    AppRoutingModule,
    FeatureFlagsModule
  ],
  providers: [
    {
      provide: FeatureFlagStore,
      useClass: AppFeatureStore
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Combining app and lazy loaded module feature flags

We assume the following comes back from the backend call to /api/module-two/features:

{
    "featureFlags": {
        "feature-five-trial" : "control",
        "feature-six-trial" : "treatment",
        "feature-module" : "two",
    },
}

And we will combine it with the data from /api/getfeaturetoggles retrieved in the AppModule.

@Injectable()
export class ModuleTwoFeatureStore implements FeatureFlagStore {
  featureFlags$: Observable<string[]>;

  constructor(httpClient: HttpClient, appFeatureStore: AppFeatureStore) {

    const retrieveLazyModuleFeatureFlags = httpClient.get<FeatureToggleData>('/api/module-two/features')
    .pipe(
      map((val) => {
      const flags: string[] = [];
      const featureFlags = Object.keys(val.featureFlags);
      for(let flag of featureFlags) {
        flags.push(`${flag}:${val.featureFlags[flag]}`);
      }
      return flags;
    }));

    this.featureFlags$ = forkJoin([
      retrieveLazyModuleFeatureFlags,
      appFeatureStore.featureFlags$
    ]).pipe(
      map(([a,b]) => [...a,...b]),
      shareReplay(1));
  }
}

And our configuration for the module:

@NgModule({
  declarations: [
    CompTwoComponent
  ],
  imports: [
    CommonModule,
    FeatureFlagsModule,
    ModuleTwoRoutingModule
  ],
  exports: [
    CompTwoComponent
  ],
  providers: [
    {
      provide: FeatureFlagStore,
      useClass: ModuleTwoFeatureStore
    }
  ]
})
export class ModuleTwoModule { }

Using a different dataset for a component and all it's childs

We assume the server returns the following:

{
    "featureFlags": {
        "feature-one-trial" : "control",
        "feature-two-trial" : "treatment",
        "feature-module" : "parent",
    },
}
import { HttpClient } from '@angular/common/http';
import { Component, Injectable } from '@angular/core';
import { FeatureFlagStore } from '@eriklieben/angular-feature-flags';
import { map, Observable, shareReplay } from 'rxjs';

@Injectable()
export class ParentComponentFeatureStore implements FeatureFlagStore {
  featureFlags$: Observable<string[]>;

  constructor(httpClient: HttpClient) {
    this.featureFlags$ = httpClient.get<FeatureToggleData>('/api/parent-component/features')
    .pipe(map((val) => {
      const flags: string[] = [];
      const featureFlags = Object.keys(val.featureFlags);
      for(let flag of featureFlags) {
        flags.push(`${flag}:${val.featureFlags[flag]}`);
      }
      return flags;
    }),
    shareReplay(1));

  }
}
interface FeatureToggleData {
  featureFlags: {[key: string]: any};
}

In the component decorator we provide the store with the observable that provides the feature flags for the given component and all it's children (if not overridden again).

@Component({
  selector: 'app-parent-component-one',
  templateUrl: './parent-component-one.component.html',
  styleUrls: ['./parent-component-one.component.scss'],
  providers: [
    {
      provide: FeatureFlagStore,
      useClass: ParentComponentFeatureStore
    }
  ]
})
export class ParentComponentOneComponent {
}

Readme

Keywords

none

Package Sidebar

Install

npm i @eriklieben/angular-feature-flags

Weekly Downloads

90

Version

16.0.0

License

none

Unpacked Size

147 kB

Total Files

32

Last publish

Collaborators

  • eriklieben