Skip to content

Angular Adapter

@useoptimus/angular provides FeatureFlagService, provideFeatureFlags, and the *ifFeature structural directive.

Terminal window
npm install @useoptimus/angular

Peer dependencies: @angular/core/@angular/common@^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0, rxjs@^7.8.0. The floor is 19 specifically because takeUntilDestroyed() (used for the directive’s cleanup) only reached stable, non-developer-preview status in Angular 19.

import { bootstrapApplication } from "@angular/platform-browser";
import { provideFeatureFlags } from "@useoptimus/angular";
bootstrapApplication(AppComponent, {
providers: [provideFeatureFlags(client, snapshot)],
});
import { Component, inject } from "@angular/core";
import { FeatureFlagService, IfFeatureDirective } from "@useoptimus/angular";
@Component({
standalone: true,
imports: [IfFeatureDirective],
template: `<div *ifFeature="'show-banner'">New banner!</div>`,
})
class BannerComponent {
private readonly featureFlagService = inject(FeatureFlagService);
variant$ = this.featureFlagService.flag$<string>("checkout-v2");
}

No per-call context parameter on flag$(key) — deliberately narrower than the React adapter’s useFlag(key, context). FlagsClient itself only exposes one ambient, mutable context field, and Angular has no per-render-scoped unit like a React hook call to safely hang a stateless override off of. Every caller shares the one ambient context; change it via featureFlagService.setContext(context).

*ifFeature uses plain Boolean(value) truthiness for any flag shape (boolean/variant/value alike) — for variant-specific branching, use flag$(key) | async with *ngSwitch directly instead.

Call provideFeatureFlags() only from root bootstrapApplication providers, not from a lazy route’s providers, unless you deliberately want a route-scoped client. FeatureFlagService never calls client.init()/client.dispose() itself — if it did, navigating away from whichever lazy route happened to register a shared, app-lifetime client would silently kill it the moment that route’s EnvironmentInjector is torn down (a real, not dev-only, event, unlike React StrictMode’s dev-only double-invoke).

Terminal window
pnpm --filter @useoptimus/core build
pnpm --filter @useoptimus/node build
pnpm --filter @useoptimus/angular test

The two build steps are required: Angular’s Karma test runner resolves @useoptimus/core/@useoptimus/node via a webpack alias to their built dist/ output rather than raw TypeScript, since Angular’s stock webpack karma builder doesn’t transform .ts reached through node_modules. Requires Node v24.15.0+/v22.22.3+/v26+ (Angular 22’s CLI version gate).