Angular
@nexafon/angular has standalone components and a service around the web SDK. Supported: Angular 22 with
RxJS 7.4 or later.
npm install @nexafon/angular1. Provide Nexafon
Section titled “1. Provide Nexafon”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:
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:
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))2. The server
Section titled “2. The server”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:
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.
3. Components and the service
Section titled “3. Components and the service”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.