Skip to content

@nexafon/angular has standalone components and a service around the web SDK. Supported: Angular 22 with RxJS 7.4 or later.

Terminal window
npm install @nexafon/angular

provideNexafon(options) takes the Nexafon.init options and starts Nexafon once, with the app. The example’s appConfig takes the publishable key as an argument:

examples/angular/src/app/app.config.ts
import { type ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'
import { provideClientHydration, withEventReplay } from '@angular/platform-browser'
import { provideNexafon } from '@nexafon/angular'
const getUserToken = async () => {
const r = await fetch('/api/nexafon-token', { method: 'POST' })
if (!r.ok) throw new Error(`The token endpoint answered ${r.status}`)
return r.text()
}
export function appConfig(publishableKey: string): ApplicationConfig {
return {
providers: [
provideBrowserGlobalErrorListeners(),
provideClientHydration(withEventReplay()),
provideNexafon({ publishableKey, getUserToken }),
],
}
}

The browser entry point asks the example’s server for the key (GET /api/nexafon-config) before it starts the app, so one build serves any workspace:

examples/angular/src/main.ts
import { bootstrapApplication } from '@angular/platform-browser'
import { App } from './app/app'
import { appConfig } from './app/app.config'
// The publishable key comes from the server when the page loads, so one build serves any workspace.
fetch('/api/nexafon-config')
.then((r) => r.json() as Promise<{ publishableKey: string }>)
.then(({ publishableKey }) => bootstrapApplication(App, appConfig(publishableKey)))
.catch((err: unknown) => console.error(err))

The example’s Express server (Angular SSR) answers GET /api/nexafon-config with NEXAFON_PUBLISHABLE_KEY and signs user tokens at POST /api/nexafon-token with NEXAFON_SECRET_KEY and the Node helper, which getUserToken calls:

examples/angular/src/server.ts
import { join } from 'node:path'
import {
AngularNodeAppEngine,
createNodeRequestHandler,
isMainModule,
writeResponseToNodeResponse,
} from '@angular/ssr/node'
import { createUserToken } from '@nexafon/node'
import express, { type Request } from 'express'
try {
process.loadEnvFile()
} catch {
// No .env: the variables come from the environment.
}
const browserDistFolder = join(import.meta.dirname, '../browser')
const app = express()
const angularApp = new AngularNodeAppEngine()
app.get('/api/nexafon-config', (_req, res) => {
res.json({ publishableKey: process.env.NEXAFON_PUBLISHABLE_KEY ?? '' })
})
interface SignedInUser {
id: string
name: string
role: string
email: string
emailVerified: boolean
isStaff: boolean
}
// Your app's own session decides who is signed in, and its own records whether it has confirmed their email. The
// example has no sign-in: EXAMPLE_DEMO_USER_EMAIL is a demo user for local runs only, so replace this with your session.
function signedInUser(_req: Request): SignedInUser | null {
const email = process.env.EXAMPLE_DEMO_USER_EMAIL
if (!email) return null
return {
id: 'demo-user-1',
name: 'Demo User',
role: process.env.EXAMPLE_DEMO_USER_ROLE || 'agent',
email,
// The demo address is a member you invited, so you have confirmed it.
emailVerified: true,
isStaff: true,
}
}
// Everyone who gets a token may reach your workspace's inbox and calls: sign only for your staff, never for customers
// who signed up. The example's only user is the demo teammate.
function mayUseNexafon(user: SignedInUser): boolean {
return user.isStaff
}
// Signs a token for the signed-in user with NEXAFON_SECRET_KEY.
app.post('/api/nexafon-token', (req, res) => {
const user = signedInUser(req)
if (!user) {
res.sendStatus(401)
return
}
if (!mayUseNexafon(user)) {
res.sendStatus(403)
return
}
const token = createUserToken({
userId: user.id,
name: user.name,
role: user.role,
email: user.email,
// Nexafon links a first sign-in to the member with this email only when it is verified: never send true for an
// address your app has not confirmed, or whoever sets a member's email on your app acts as that member.
emailVerified: user.emailVerified,
})
res.type('text/plain').set('cache-control', 'no-store').send(token)
})
app.use(express.static(browserDistFolder, { maxAge: '1y', index: false, redirect: false }))
app.use((req, res, next) => {
angularApp
.handle(req)
.then((response) => (response ? writeResponseToNodeResponse(response, res) : next()))
.catch(next)
})
if (isMainModule(import.meta.url)) {
const port = process.env.PORT || 5205
app.listen(Number(port), '127.0.0.1', (error) => {
if (error) throw error
console.log(`Angular example on port ${port}`)
})
}
export const reqHandler = createNodeRequestHandler(app)

signedInUser stands for your app’s own session: answer 401 when nobody is signed in. The example has no sign-in, so on a local run it signs for the demo member named in EXAMPLE_DEMO_USER_EMAIL and answers 401 without one; replace it with your session. mayUseNexafon answers 403 to signed-in users who must not use Nexafon: sign only for your staff, never for customers who signed up, since a token can make its user a member of your workspace. The example treats its demo user as staff.

examples/angular/src/app/app.ts
import { Component } from '@angular/core'
import { CallButtonComponent, InboxComponent, SoftphoneComponent } from '@nexafon/angular'
@Component({
selector: 'app-root',
imports: [InboxComponent, SoftphoneComponent, CallButtonComponent],
template: `
<main class="support">
<nexafon-ng-inbox style="height: 640px" />
<aside>
<nexafon-ng-call-button phone="+252615550142" />
<nexafon-ng-softphone />
</aside>
</main>
`,
styles: `.support { display: grid; gap: 16px; padding: 16px; grid-template-columns: 1fr 340px; }`,
})
export class App {}
Component Renders Inputs
<nexafon-ng-app> <nexafon-app> store
<nexafon-ng-inbox> <nexafon-inbox> store
<nexafon-ng-softphone> <nexafon-softphone> store
<nexafon-ng-call-button> <nexafon-call-button> phone, store

Each renders inside a block host you size. NexafonService.events(name) is an Observable of one event; openChat, call, startInternalCall and setAppearance call the started SDK synchronously, so a pop-out window still opens inside the user’s click; api is a signal of the SDK (null on the server and until it has started).

On the server (isPlatformBrowser is false) the service never loads Nexafon and the components render an empty host; the browser creates the elements after hydration. The full example is examples/angular.