Skip to main content

Angular SDK Integration

3. Install the SDK

# Using npm
npm install @flagpole/angular socket.io-client

# Using yarn
yarn add @flagpole/angular socket.io-client

4. Initialize in Your Application

// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FeatureFlagModule } from "@flagpole/angular";

import { AppComponent } from "./app.component";

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
FeatureFlagModule.forRoot({
apiKey: "fp_live_your_api_key",
environments: ["development"], // optional, if nothing is passed, then all environments will be shown (production, staging and development)
}),
],
bootstrap: [AppComponent],
})
export class AppModule {}

5. Use Feature Flags

Template Usage

<!-- feature.component.html -->
<div>
<h2>Feature Flags Test</h2>

<!-- Loading state -->
<div *ngIf="isLoading$ | async">Loading flags...</div>

<!-- Error state -->
<div *ngIf="error$ | async as error" class="error">
Error: {{ error.message }}
</div>

<!-- Use structural directive for specific flag -->
<div *flagpoleFeature="'newFeature'">
<app-new-feature></app-new-feature>
</div>

<!-- Use directive with fallback -->
<div *flagpoleFeature="'betaFeature'; else oldFeature">
<h3>Beta Feature Content</h3>
</div>
<ng-template #oldFeature>
<h3>Old Feature Content</h3>
</ng-template>

<!-- Use pipe for inline checks -->
<button *ngIf="'premiumFeature' | featureFlag" class="premium-btn">
Premium Action
</button>

<!-- Display all available flags -->
<h3>All Flags:</h3>
<div *ngFor="let flag of (flags$ | async) | keyvalue" class="flag-item">
<strong>{{ flag.key }}:</strong>
<span [class]="flag.value.isEnabled ? 'enabled' : 'disabled'">
{{ flag.value.isEnabled ? 'Enabled' : 'Disabled' }}
</span>
</div>
</div>

Component Usage

// feature.component.ts
import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs";
import { FeatureFlagService, FeatureFlag } from "@flagpole/angular";

@Component({
selector: "app-feature",
templateUrl: "./feature.component.html",
styleUrls: ["./feature.component.css"],
})
export class FeatureComponent implements OnInit {
flags$: Observable<Record<string, FeatureFlag>>;
isLoading$: Observable<boolean>;
error$: Observable<Error | null>;

constructor(private featureFlagService: FeatureFlagService) {
this.flags$ = this.featureFlagService.flags$;
this.isLoading$ = this.featureFlagService.isLoading$;
this.error$ = this.featureFlagService.error$;
}

ngOnInit(): void {
// Check if a specific feature is enabled
if (this.featureFlagService.isFeatureEnabled("newDashboard")) {
console.log("New dashboard is enabled");
}

// Get complete flag details
const flag = this.featureFlagService.getFlag("betaFeature");
if (flag) {
console.log("Flag details:", flag);
}
}

onButtonClick(): void {
const isEnabled = this.featureFlagService.isFeatureEnabled("buttonFeature");
if (isEnabled) {
console.log("Button feature is enabled - executing action");
// Your feature logic here
}
}
}

Available APIs

FeatureFlagService

Methods

// Check if a feature flag is enabled
isFeatureEnabled(flagName: string): boolean

// Get complete flag details
getFlag(flagName: string): FeatureFlag | null

// Get all flags
getAllFlags(): Record<string, FeatureFlag>

Observables

// All feature flags
flags$: Observable<Record<string, FeatureFlag>>;

// Loading state
isLoading$: Observable<boolean>;

// Error state
error$: Observable<Error | null>;

Structural Directive

Use the *flagpoleFeature directive to conditionally show/hide content:

<!-- Basic usage -->
<div *flagpoleFeature="'featureName'">Feature content here</div>

<!-- With else template -->
<div *flagpoleFeature="'featureName'; else fallback">New feature content</div>
<ng-template #fallback> Old feature content </ng-template>

Pipe

Use the featureFlag pipe for inline flag checks:

<!-- Show element if flag is enabled -->
<button *ngIf="'featureName' | featureFlag">Action</button>

<!-- Conditional classes -->
<div [class.premium]="'premiumFeature' | featureFlag">
Content with conditional styling
</div>

Route Guards

Protect routes based on feature flags:

// app-routing.module.ts
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { FeatureFlagGuard } from "@flagpole/angular";

const routes: Routes = [
{
path: "beta-feature",
component: BetaComponent,
canActivate: [FeatureFlagGuard],
data: {
featureFlag: "betaAccess",
redirectTo: "/home", // Optional: redirect if flag is disabled
},
},
{
path: "admin",
loadChildren: () =>
import("./admin/admin.module").then((m) => m.AdminModule),
canActivate: [FeatureFlagGuard],
data: {
featureFlag: "adminPanel",
redirectTo: "/unauthorized",
},
},
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}

Best Practices

API Key Security

  • Store API keys in environment variables
  • Use different keys for different environments
  • Never commit API keys to source control
  • Rotate keys if they're ever exposed
// Example using environment variables
// environment.ts
export const environment = {
production: false,
flagpoleApiKey: "fp_dev_your_dev_api_key",
};

// app.module.ts
import { environment } from "../environments/environment";

@NgModule({
imports: [
FeatureFlagModule.forRoot({
apiKey: environment.flagpoleApiKey,
environments: ["development"],
}),
],
})
export class AppModule {}

Error Handling

Always handle loading and error states in your templates:

<!-- Loading state -->
<div *ngIf="isLoading$ | async" class="loading">
<mat-spinner></mat-spinner>
Loading feature flags...
</div>

<!-- Error state -->
<div *ngIf="error$ | async as error" class="error-banner">
<mat-icon>error</mat-icon>
Failed to load feature flags: {{ error.message }}
</div>

<!-- Content when loaded -->
<div *ngIf="!(isLoading$ | async) && !(error$ | async)">
<!-- Your feature content here -->
</div>

Reactive Patterns

Leverage RxJS for reactive programming:

import { Component } from "@angular/core";
import { combineLatest, map } from "rxjs";

@Component({
selector: "app-dashboard",
template: `
<div *ngIf="showAdvancedDashboard$ | async; else basicDashboard">
<app-advanced-dashboard></app-advanced-dashboard>
</div>
<ng-template #basicDashboard>
<app-basic-dashboard></app-basic-dashboard>
</ng-template>
`,
})
export class DashboardComponent {
showAdvancedDashboard$ = combineLatest([
this.featureFlagService.flags$,
this.userService.currentUser$,
]).pipe(
map(
([flags, user]) =>
flags["advancedDashboard"]?.isEnabled && user?.isPremium
)
);

constructor(
private featureFlagService: FeatureFlagService,
private userService: UserService
) {}
}

Feature Flag Naming

Use descriptive, consistent names:

  • Include feature context
  • Use camelCase
  • Be specific but concise

Examples:

  • newDashboard
  • betaUserProfile
  • experimentalSearch

Testing

Test both enabled and disabled states:

// feature.component.spec.ts
describe("FeatureComponent", () => {
let component: FeatureComponent;
let featureFlagService: jasmine.SpyObj<FeatureFlagService>;

beforeEach(() => {
const spy = jasmine.createSpyObj("FeatureFlagService", [
"isFeatureEnabled",
]);

TestBed.configureTestingModule({
declarations: [FeatureComponent],
providers: [{ provide: FeatureFlagService, useValue: spy }],
});

featureFlagService = TestBed.inject(
FeatureFlagService
) as jasmine.SpyObj<FeatureFlagService>;
});

it("should show new feature when flag is enabled", () => {
featureFlagService.isFeatureEnabled.and.returnValue(true);

// Test enabled state
});

it("should hide new feature when flag is disabled", () => {
featureFlagService.isFeatureEnabled.and.returnValue(false);

// Test disabled state
});
});

Performance Considerations

  • Use OnPush change detection strategy when possible
  • Unsubscribe from observables to prevent memory leaks
  • Use the async pipe for automatic subscription management
@Component({
selector: "app-feature",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div *flagpoleFeature="'newFeature'">
<!-- Content automatically updates when flag changes -->
</div>
`,
})
export class FeatureComponent implements OnDestroy {
private destroy$ = new Subject<void>();

constructor(private featureFlagService: FeatureFlagService) {
// Manual subscription example
this.featureFlagService.flags$
.pipe(takeUntil(this.destroy$))
.subscribe((flags) => {
// Handle flags update
});
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}