DevOps intermediate

Angular Feature Toggles (aka Feature Flags) Using LaunchDarkly

Complete guide to integrating LaunchDarkly SDK with Angular for reactive feature flag management using RxJS observables.

Alex Muturi
October 18, 2022
5 min read
#Angular #Feature Flags #LaunchDarkly #RxJS #DevOps #Feature Toggles

Angular Feature Toggles (aka Feature Flags) Using LaunchDarkly

📌 Legacy Content Notice: This article was published in October 2022 and uses Angular patterns that were standard at the time (constructor injection, *ngIf directive). For modern Angular 17+ applications, consider using:

  • inject() function instead of constructor injection
  • @if control flow instead of *ngIf
  • input() and output() functions instead of decorators
  • ChangeDetectionStrategy.OnPush for all components

The LaunchDarkly integration concepts remain valid; only the Angular syntax has evolved.

Feature toggles (also known as feature flags) are a powerful technique that allows you to enable or disable features in your application without deploying new code. This article demonstrates how to integrate LaunchDarkly's feature flag service with an Angular application using RxJS for reactive updates.

What Are Feature Flags?

Feature flags allow you to:

  • Release features gradually to specific user segments
  • Test in production safely with beta users
  • A/B test different features or implementations
  • Kill switches to quickly disable problematic features
  • Canary releases to roll out changes incrementally

Prerequisites

  • Angular application (v12+)
  • LaunchDarkly account (free tier available)
  • Basic knowledge of RxJS observables

Step 1: Create a LaunchDarkly Account

  1. Sign up at https://launchdarkly.com/
  2. Create a new project
  3. Get your Client-side ID from the project settings (not the SDK key)

Step 2: Install LaunchDarkly SDK

Install the LaunchDarkly JavaScript client SDK:

npm install launchdarkly-js-client-sdk

Step 3: Create a Feature Flag Service

Create a service to manage LaunchDarkly client and feature flags:

import { Injectable } from '@angular/core';
import * as LDClient from 'launchdarkly-js-client-sdk';
import { Observable, BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class FeatureFlagService {
  private ldClient!: LDClient.LDClient;
  private flagSubjects = new Map<string, BehaviorSubject<any>>();

  constructor() {}

  /**
   * Initialize LaunchDarkly client
   */
  async initialize(clientSideId: string, userContext: LDClient.LDUser): Promise<void> {
    this.ldClient = LDClient.initialize(clientSideId, userContext);
    
    await this.ldClient.waitForInitialization();
    console.log('LaunchDarkly initialized');
  }

  /**
   * Get a feature flag value as an Observable
   */
  getFlag(flagKey: string, defaultValue: any = false): Observable<any> {
    if (!this.flagSubjects.has(flagKey)) {
      const currentValue = this.ldClient.variation(flagKey, defaultValue);
      const subject = new BehaviorSubject<any>(currentValue);
      
      // Listen for flag changes
      this.ldClient.on(`change:${flagKey}`, (newValue: any) => {
        subject.next(newValue);
      });
      
      this.flagSubjects.set(flagKey, subject);
    }
    
    return this.flagSubjects.get(flagKey)!.asObservable();
  }

  /**
   * Get all flags
   */
  getAllFlags(): LDClient.LDFlagSet {
    return this.ldClient.allFlags();
  }

  /**
   * Update user context
   */
  async identify(user: LDClient.LDUser): Promise<void> {
    await this.ldClient.identify(user);
  }

  /**
   * Close the connection
   */
  close(): void {
    this.ldClient.close();
  }
}

Step 4: Initialize in App Component

Initialize the LaunchDarkly client when your app starts:

import { Component, OnInit } from '@angular/core';
import { FeatureFlagService } from './services/feature-flag.service';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `<h1>App Root</h1>`
})
export class AppComponent implements OnInit {
  constructor(private featureFlagService: FeatureFlagService) {}

  async ngOnInit() {
    const userContext = {
      key: 'user-123', // Unique user identifier
      email: 'user@example.com',
      name: 'John Doe',
      custom: {
        role: 'admin',
        plan: 'premium'
      }
    };

    await this.featureFlagService.initialize(
      environment.launchDarklyClientId,
      userContext
    );
  }
}

Step 5: Use Feature Flags in Components

Now you can use feature flags reactively in your components:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FeatureFlagService } from './services/feature-flag.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div *ngIf="showNewFeature$ | async">
      <h2>New Feature!</h2>
      <p>This feature is controlled by LaunchDarkly</p>
    </div>

    <button [disabled]="!(enableButton$ | async)">
      Click me
    </button>
  `
})
export class DashboardComponent implements OnInit {
  showNewFeature$!: Observable<boolean>;
  enableButton$!: Observable<boolean>;

  constructor(private featureFlagService: FeatureFlagService) {}

  ngOnInit() {
    this.showNewFeature$ = this.featureFlagService.getFlag('show-new-feature', false);
    this.enableButton$ = this.featureFlagService.getFlag('enable-button', true);
  }
}

Step 6: Environment Configuration

Add your LaunchDarkly client ID to environment files:

environment.ts:

const environment = {
  production: false,
  launchDarklyClientId: 'your-client-side-id'
};

environment.prod.ts:

const environmentProd = {
  production: true,
  launchDarklyClientId: 'your-production-client-side-id'
};

Event Streaming and Real-Time Updates

LaunchDarkly uses WebSocket connections to stream flag changes in real-time. The service automatically listens for changes:

function watchFlagChanges(featureFlagService: FeatureFlagService) {
  // Flags update automatically via WebSocket
  featureFlagService.getFlag('my-flag').subscribe((value: any) => {
    console.log('Flag updated:', value);
    // React to flag changes
  });
}

User Context and Targeting

LaunchDarkly supports sophisticated user targeting. You can use built-in attributes or custom attributes:

const userContext = {
  key: 'user-123',              // Required: unique identifier
  email: 'user@example.com',    // Built-in attribute
  name: 'John Doe',             // Built-in attribute
  country: 'US',                // Built-in attribute
  custom: {                      // Custom attributes
    role: 'admin',
    subscription: 'premium',
    accountAge: 365,
    betaTester: true
  }
};

Advanced: Feature Flag Guards

Create route guards based on feature flags:

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { FeatureFlagService } from './services/feature-flag.service';
import { take, map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class FeatureFlagGuard implements CanActivate {
  constructor(
    private featureFlagService: FeatureFlagService,
    private router: Router
  ) {}

  canActivate() {
    return this.featureFlagService.getFlag('new-feature-enabled', false)
      .pipe(
        take(1),
        map(enabled => {
          if (!enabled) {
            this.router.navigate(['/']);
            return false;
          }
          return true;
        })
      );
  }
}

Use in routes:

import { Routes } from '@angular/router';

@Component({ standalone: true, template: '' })
export class NewFeatureComponent {}

const routes: Routes = [
  {
    path: 'new-feature',
    component: NewFeatureComponent,
    canActivate: [FeatureFlagGuard]
  }
];

Best Practices

  1. Use descriptive flag names: enable-dark-mode instead of flag1
  2. Always provide default values: Ensure your app works if LaunchDarkly is unavailable
  3. Clean up old flags: Remove flags and related code after full rollout
  4. Use targeting rules: Target specific user segments before full rollout
  5. Monitor flag changes: Track when and how flags are used

Conclusion

LaunchDarkly integration with Angular provides a powerful way to manage feature releases dynamically. By combining LaunchDarkly's real-time updates with RxJS observables, you get a reactive feature flag system that seamlessly integrates with Angular's async pipe and change detection. This approach enables sophisticated release strategies without requiring code deployments.

Share this article