Skip to main content

Angular example

Wire up one canonical anonymous id and fan a single identify() call out to Segment, LaunchDarkly, and FullStory — from an Angular app. See Architecture for the concepts behind IdentityProvider/IdentityClient.

1. Install

npm install @idhub/identity-core @idhub/identity-adapter-angular \
@idhub/identity-provider-segment @idhub/identity-provider-launchdarkly @idhub/identity-provider-fullstory

2. AppModule — register IdentityModule.forRoot()

// app.module.ts
import { NgModule } from '@angular/core'
import { IdentityModule } from '@idhub/identity-adapter-angular'
import { segmentProvider } from '@idhub/identity-provider-segment'
import { launchDarklyProvider } from '@idhub/identity-provider-launchdarkly'
import { fullStoryProvider } from '@idhub/identity-provider-fullstory'
import { analytics } from './segment-client' // your own analytics-node / analytics.js instance

@NgModule({
imports: [
IdentityModule.forRoot({
providers: [
segmentProvider({ client: analytics, writeKey: environment.segmentWriteKey }),
launchDarklyProvider({ clientSideId: environment.launchDarklyClientId }),
fullStoryProvider({ orgId: environment.fullStoryOrgId }),
],
cookie: { name: '_anon_id', domain: '.example.com' },
enableHttpInterceptor: true, // optional: adds X-Identity-Id to every HttpClient request
}),
],
})
export class AppModule {}

forRoot() registers IdentityService, an APP_INITIALIZER that blocks bootstrap until the anonymous id is resolved and every provider's onAnonymous has fired, and — only when enableHttpInterceptor: true — the IdentityHttpInterceptor. Call forRoot() exactly once, in the root module: importing bare IdentityModule in a lazy feature module registers nothing on purpose, so a second IdentityClient (and a second, competing anonymous id) can't get created by accident.

3. Inject IdentityService and call identify()

// header.component.ts
import { Component, Inject } from '@angular/core'
import { IdentityService } from '@idhub/identity-adapter-angular'

@Component({ selector: 'app-header', templateUrl: './header.component.html' })
export class HeaderComponent {
readonly state$ = this.identity.state$ // Observable<IdentityState>

constructor(@Inject(IdentityService) private readonly identity: IdentityService) {}

onLogin(userId: string) {
this.identity.identify(userId, { plan: 'pro' })
}

onLogout() {
this.identity.reset()
}
}

One identify() call fans out to onIdentify on all three providers — Segment (identify(userId, traits)), LaunchDarkly (multi-kind device + user context, replacing the removed alias()), and FullStory (setIdentity, which auto-merges the anonymous session). reset() on logout issues a brand-new anonymous id rather than reusing the pre-login one.

That's the full client-side integration: one forRoot() call in AppModule and IdentityService injected wherever you need state$/identify()/reset()/track()/page().

4. SSR caveat: Angular Universal needs the companion Express middleware

APP_INITIALIZER only guarantees the anonymous id exists before the first route renders in the browser — it runs during client bootstrap. Angular has no request-time middleware equivalent to Next.js's Edge middleware, so on a server-rendered (Angular Universal) app, the very first request has no mechanism of its own to mint the anonymous-id cookie before that first server-rendered response goes out. Without a request-time hook, the server render and the client bootstrap that follows it can each end up minting a different anonymous id — the exact "two ids racing" bug this package exists to prevent.

If your app uses Angular Universal, mount the companion Express middleware from the package's /server entry point in the Node server that hosts the Universal build:

// server.ts (Angular Universal Express server)
import { identityExpressMiddleware } from '@idhub/identity-adapter-angular/server'

server.use(identityExpressMiddleware({ cookie: { name: '_anon_id' } }))
// downstream: req.anonymousId is set, and Set-Cookie is appended (not replaced,
// so it composes with session/auth cookies set by other middleware)

This is a separate entry point on purpose — it never gets pulled into browser bundles. It uses identity-core's cookie primitives directly (not the Web Standard Request-based helper the Next.js adapter uses) because Express exposes a Node IncomingMessage, so there is no duplicated cookie logic, only a different transport. With this middleware mounted, the same anonymous id that the server used to render the first response is the one the browser's APP_INITIALIZER bootstrap reads from the cookie — the two guarantees compose instead of racing.

Building a Next.js app instead? See the Next.js example.