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.