A comprehensive Angular library for integrating LaunchDarkly feature flags with reactive directives and services.
NOTE: This library is not officially supported by LaunchDarkly.
npm install @launchtarqly/launchdarkly-angular
import { APP_INITIALIZER, NgModule, NgZone } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { LaunchDarklyService, LD_SERVICE_CONFIG, LaunchDarklyAngularModule } from '@launchtarqly/launchdarkly-angular';
import { environment } from '../environments/environment';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
LaunchDarklyAngularModule
],
providers: [
{
provide: LD_SERVICE_CONFIG,
useValue: {
/* your LaunchDarkly client-side ID */
clientId: environment.launchDarklyClientId,
/* context to start with, should be set as early. Avoid using dummy contexts */
context: { key: 'demo-user', name: 'Demo User' },
/* LDOptions passed to the LaunchDarkly Client */
options: {
streaming: true
bootstrap: "localStorage"
}
}
},
// Prevent a flash of fallback values by waiting for at most 200ms for LaunchDarkly to be ready
// for angular 17+, use provideAppInitializer(LaunchDarklyService.createAppInitializer(200)) instead
{
provide: APP_INITIALIZER,
useFactory: LaunchDarklyService.createAppInitializer(200),
deps: [LaunchDarklyService],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
<!-- Conditional rendering -->
<div *ldIf="'new-feature'; fallback: false">New feature content</div>
<!-- Conditional CSS classes -->
<div [ldClassIf]="'premium-features'" [ldClassIfClass]="'premium-user'">
User content
</div>
<!-- Event tracking -->
<button [ldTrack]="'button-clicked'">Click me</button>
import { LaunchDarklyService } from '@launchtarqly/launchdarkly-angular';
@Component({...})
export class MyComponent {
constructor(private ld: LaunchDarklyService) {}
ngOnInit() {
this.ld.variation$('new-feature', false).subscribe(enabled => {
if (enabled) {
this.showNewFeature();
}
});
}
}
Conditionally renders content based on feature flags.
Example :<!-- Simple boolean flag -->
<div *ldIf="'new-feature'; fallback: false">New feature content</div>
<!-- String flag with specific value -->
<div *ldIf="'user-tier'; fallback: 'basic'; value: 'premium'">Premium content</div>
<!-- Number flag -->
<div *ldIf="'max-items'; fallback: 5; value: 10">Show 10 items</div>
Universal directive for any flag type with template injection.
Example :<!-- String flag with text injection -->
<ng-template [ldFlag]="'welcome-message'" [ldFlagFallback]="'Welcome!'" let-message>
<h1>{{ message }}</h1>
</ng-template>
Switch-like conditional rendering for multi-variant flags.
Example :<ng-container [ldSwitch]="'user-tier'" [ldSwitchFallback]="'basic'">
<ng-template [ldSwitchCase]="'basic'">
<div>Basic user features</div>
</ng-template>
<ng-template [ldSwitchCase]="'premium'">
<div>Premium user features</div>
</ng-template>
<ng-template ldSwitchDefault>
<div>Unknown user tier</div>
</ng-template>
</ng-container>
Conditionally applies CSS classes.
Example :<!-- Apply class when flag is truthy -->
<div [ldClassIf]="'enable-premium-features'" [ldClass]="'premium-user'">
User content
</div>
<!-- Use a fallback value if initialization fails -->
<div [ldClassIf]="'enable-premium-features'" [ldClassIfFallback]="false" [ldClass]="'premium-user'">
User content
</div>
<!-- Apply different classes based matching a specific value -->
<div [ldClassIf]="'config-user-tier'"
[ldClassIfValue]="'premium'"
[ldClassIfClass]="'premium-user'"
[ldClassIfElseClass]="'basic-user'">
User content
</div>
Conditionally applies inline CSS styles.
Example :<!-- Apply styles when flag is truthy -->
<div [ldStyleIf]="'premium-features'" [ldStyle]="{backgroundColor: '#007bff', color: 'white'}">
Premium content
</div>
<!-- Apply different styles based on condition -->
<div [ldStyleIf]="'user-tier'"
[ldStyleIfValue]="'premium'"
[ldStyle]="{background: 'linear-gradient(45deg, #ffd700, #ffed4e)', color: '#8b4513'}"
[ldElseStyle]="{backgroundColor: '#e9ecef', color: '#495057'}">
User content
</div>
Automatically tracks events for analytics.
Example :<!-- Simple click tracking -->
<button [ldTrack]="'button-clicked'">Click me</button>
<!-- Track with custom data -->
<button [ldTrack]="'purchase'" [ldTrackData]="{product: 'premium', price: 29.99}">
Buy Premium
</button>
<!-- Track conversion with metric value -->
<button [ldTrack]="'conversion'" [ldTrackValue]="29.99" [ldTrackData]="{product: 'premium'}">
Buy Premium
</button>
<!-- Track on different events -->
<div [ldTrack]="'hover-detected'" [ldTrackEvent]="'mouseenter'" [ldTrackData]="{section: 'hero'}">
Hover me
</div>
The main service for feature flag management.
variation$(key: string, fallback: T): Observable<T>
Gets the value of a feature flag as an observable stream.
Example :this.ldService.variation$('new-feature', false).subscribe(enabled => {
if (enabled) {
this.showNewFeature();
}
});
variationDetail$(key: string, fallback: T): Observable<LDEvaluationDetail>
Gets detailed evaluation information for a feature flag.
Example :this.ldService.variationDetail$('pricing-tier', 'basic').subscribe(detail => {
console.log(`Flag value: ${detail.value}`);
console.log(`Reason: ${detail.reason.kind}`);
});
setContext(context: LDContext, timeoutMs?: number): Promise<void>
Changes the user context for the LaunchDarkly client.
Example :const newContext = { key: 'user123', email: 'user@example.com' };
try {
await this.ldService.setContext(newContext, 5000);
console.log('Context updated successfully');
} catch (err) {
console.error('Failed to update context', err);
}
track(key: string, data?: any, metricValue?: number): void
Tracks a custom event for analytics and experimentation.
Example :// Track a simple event
this.ldService.track('button-clicked');
// Track an event with data
this.ldService.track('purchase-completed', { productId: 'abc123', amount: 99.99 });
// Track an event with metric value
this.ldService.track('page-view', { page: '/dashboard' }, 1);
flush(): Promise<void>
Forces the LaunchDarkly client to flush any pending analytics events.
Example :await this.ldService.flush();
waitUntilReady$(timeoutMs: number): Observable<boolean>
Waits for LaunchDarkly to be ready with a timeout.
Example :this.ldService.waitUntilReady$(2000).subscribe(isReady => {
if (isReady) {
console.log('LaunchDarkly is ready');
} else {
console.log('Still waiting for LaunchDarkly...');
}
});
LaunchDarklyAngularModule.forRoot({
clientId: 'your-client-id',
context: { key: 'user123', name: 'John Doe' },
options: {
streaming: true,
debug: false
},
timeout: 500
})
clientId
: Your LaunchDarkly client-side IDcontext
: User context for flag evaluationoptions
: LaunchDarkly client optionstimeout
: Timeout for initialization in millisecondsFor applications that need to wait for LaunchDarkly to be ready before starting:
Example :import { APP_INITIALIZER } from '@angular/core';
import { LaunchDarklyService } from '@launchtarqly/launchdarkly-angular';
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
useFactory: LaunchDarklyService.createAppInitializer(1000),
deps: [LaunchDarklyService],
multi: true
}
]
})
export class AppModule {}
# Build the library
npm run build
# Build for production
npm run build:prod
# Run tests once
npm test
# Run tests in watch mode
npm run test:watch
# Start the demo application
npm run demo:start
# Build the demo
npm run demo:build
Check out the examples/demo
directory for a complete working example of the library in action. The demo includes:
import { LaunchDarklyService } from '@launchtarqly/launchdarkly-angular';
describe('MyComponent', () => {
let mockLdService: jasmine.SpyObj<LaunchDarklyService>;
beforeEach(() => {
mockLdService = jasmine.createSpyObj('LaunchDarklyService', ['variation$']);
mockLdService.variation$.and.returnValue(of(true));
});
it('should show feature when flag is enabled', () => {
// Test implementation
});
});
import { LaunchDarklyAngularModule } from '@launchtarqly/launchdarkly-angular';
TestBed.configureTestingModule({
imports: [
LaunchDarklyAngularModule.forRoot({
clientId: 'test-client-id',
context: { key: 'test-user' }
})
]
});
Prefer directives for conditional rendering and styling:
Example :<!-- Good: Use directive -->
<div *ldIf="'new-feature'; fallback: false">New feature</div>
<!-- Avoid: Manual subscription in component -->
<div *ngIf="showFeature">New feature</div>
Use the service for complex business logic:
Example :// Good: Complex logic in component
this.ldService.variation$('pricing-tier', 'basic').subscribe(tier => {
this.calculatePricing(tier);
this.updateUI(tier);
});
Always provide meaningful fallback values:
Example :// Good: Meaningful fallback
this.ldService.variation$('welcome-message', 'Welcome!')
// Avoid: Generic fallback
this.ldService.variation$('welcome-message', null)
Use the waitUntilReady$
method for critical features:
this.ldService.waitUntilReady$(1000).subscribe(isReady => {
if (isReady) {
this.loadCriticalFeatures();
} else {
this.showLoadingState();
}
});
streaming: true
is set in optionssetContext
method with timeoutEnable debug mode for troubleshooting:
Example :LaunchDarklyAngularModule.forRoot({
clientId: 'your-client-id',
context: { key: 'user123' },
options: {
streaming: true,
debug: true
}
})
MIT License - see LICENSE file for details.