Imported from cometchat/cometchat-skills (
skills/cometchat-angular-patterns/SKILL.md). Install upstream withnpx skills add cometchat/cometchat-skills --skill cometchat-angular-patterns. Copyright stays with the author (MIT).
Ground truth:
@cometchat/chat-uikit-angular@5.x(+@cometchat/calls-sdk-javascript@^5) — installed package types +ui-kit/angular. Official docs: https://www.cometchat.com/docs/ui-kit/angular/overview · Docs MCP:claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp(or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.
Purpose
Angular-specific wiring for CometChat UI Kit v5 — how to hook init into Angular's standalone bootstrap, lazy-load chat routes, guard them, keep SDK callbacks inside Angular's zone, and handle SSR. Assumes the base integration from cometchat-angular-core + components from cometchat-angular-components.
v5 is standalone-first (Angular 17–21). Everything below uses bootstrapApplication + app.config.ts + functional APIs. There is no NgModule, no AppModule, no declarations, no CUSTOM_ELEMENTS_SCHEMA — those are v4 patterns and must not appear in a v5 app.
1. Initialize in main.ts — before bootstrapApplication
The v5 canonical (matches the kit's own sample app and the docs): call CometChatUIKit.init() in main.ts and bootstrap the Angular app inside the resolved init promise. This guarantees the SDK is ready before any <cometchat-*> component can mount. init() is a static method, so it works fine outside Angular's DI context.
// src/main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { CometChatUIKit, UIKitSettingsBuilder } from "@cometchat/chat-uikit-angular";
import { AppComponent } from "./app/app.component";
import { appConfig } from "./app/app.config";
import { environment } from "./environments/environment";
const settings = new UIKitSettingsBuilder()
.setAppId(environment.cometchat.appId)
.setRegion(environment.cometchat.region)
.setAuthKey(environment.cometchat.authKey)
.subscribePresenceForAllUsers()
// .setCallingEnabled(true) // uncomment + install @cometchat/calls-sdk-javascript for voice/video
.build();
// `init(...)` is `Promise<InitResult> | undefined` (v5.0.2) — coalesce to a Promise so
// `.then`/`.catch` don't fail `tsc` strict (TS2532) on Angular 21.
(CometChatUIKit.init(settings) ?? Promise.resolve())
.then(() => {
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
})
.catch((error) => {
console.error("CometChat UIKit init failed:", error);
// Still bootstrap so the app renders (it can show a "chat unavailable" state).
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
});
// src/app/app.config.ts — providers only; init does NOT live here
import { ApplicationConfig } from "@angular/core";
import { provideRouter } from "@angular/router";
import { routes } from "./app.routes";
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)],
};
// src/environments/environment.ts
export const environment = {
production: false,
cometchat: {
appId: "APP_ID", // Dashboard → Your App → Credentials
region: "REGION", // us | eu | in
authKey: "AUTH_KEY", // dev/testing only — use Auth Tokens in production
},
};
Don't use
APP_INITIALIZERfor this. The kit's sample app and docs both init inmain.tsand bootstrap inside.then()— that's the v5 canonical and avoids the DI-timing edge cases of an init factory provider. Always.catch()so a network/credential failure degrades gracefully instead of leaving a blank app.
2. Login + functional route guard
Init readies the SDK; login establishes the session. Guard chat routes with a functional CanActivateFn (the v17+ idiom — no class guard, no Injectable). This mirrors the sample app's authGuard:
// src/app/guards/auth.guard.ts
import { CanActivateFn, Router } from "@angular/router";
import { inject } from "@angular/core";
import { CometChat } from "@cometchat/chat-sdk-javascript";
export const authGuard: CanActivateFn = async () => {
const router = inject(Router);
try {
const user = await CometChat.getLoggedinUser(); // async; note lowercase 'in'
if (user) return true;
return router.createUrlTree(["/login"]);
} catch {
return router.createUrlTree(["/login"]);
}
};
For dev convenience you can auto-login a seeded user instead of redirecting:
import { CometChatUIKit } from "@cometchat/chat-uikit-angular";
// …inside the guard, when no user is found:
try {
await CometChatUIKit.login("cometchat-uid-1"); // bare UID string in v5 — NOT login({ uid })
return true;
} catch {
return router.createUrlTree(["/login"]);
}
Two real session-check APIs exist; pick by context:
CometChat.getLoggedinUser()/CometChatUIKit.getLoggedinUser()— async (returnsPromise<User | null>, lowercasein). Use in guards/async flows. This is what the sample app guards use.CometChatUIKit.getLoggedInUser()— sync (returnsUser | null, capitalI). Use inside components for a synchronous read after the session is known to exist.
// src/app/app.routes.ts
import { Routes } from "@angular/router";
import { authGuard } from "./guards/auth.guard";
export const routes: Routes = [
{
path: "chat",
canActivate: [authGuard],
// Lazy-load a standalone component — no NgModule, no loadChildren:
loadComponent: () => import("./chat/chat.component").then((m) => m.ChatComponent),
},
{ path: "", redirectTo: "chat", pathMatch: "full" },
];
3. Lazy-loaded standalone chat route
The chat surface is a standalone component that imports only the CometChat components it renders.
// chat/chat.component.ts
import { Component } from "@angular/core";
import {
CometChatConversationsComponent,
CometChatMessageHeaderComponent,
CometChatMessageListComponent,
CometChatMessageComposerComponent,
} from "@cometchat/chat-uikit-angular";
import { CometChat } from "@cometchat/chat-sdk-javascript";
@Component({
selector: "app-chat",
standalone: true,
imports: [
CometChatConversationsComponent,
CometChatMessageHeaderComponent,
CometChatMessageListComponent,
CometChatMessageComposerComponent,
],
templateUrl: "./chat.component.html",
})
export class ChatComponent {
selectedUser?: CometChat.User;
onConversation(conversation: CometChat.Conversation) {
const entity = conversation.getConversationWith();
if (entity instanceof CometChat.User) this.selectedUser = entity;
}
}
loadComponent (not loadChildren) is the standalone way to lazy-load — there's no feature NgModule to point at.
4. NgZone — keep SDK callbacks inside Angular's zone
CometChat SDK callbacks (event listeners, Promise resolutions from non-Angular timers) can fire outside Angular's zone, so a view bound to the result won't update until the next tick. If a UI value set inside an SDK callback isn't refreshing, re-enter the zone:
import { Component, NgZone } from "@angular/core";
import { CometChat } from "@cometchat/chat-sdk-javascript";
@Component({ /* … */ })
export class PresenceComponent {
status = "offline";
constructor(private zone: NgZone) {}
ngOnInit() {
CometChat.addUserListener("presence", new CometChat.UserListener({
onUserOnline: (user: CometChat.User) =>
this.zone.run(() => { this.status = "online"; }), // ← re-enter zone
onUserOffline: (user: CometChat.User) =>
this.zone.run(() => { this.status = "offline"; }),
}));
}
}
The kit's own <cometchat-*> components handle this internally — you only need zone.run() for your own code that mutates view state inside an SDK callback. Prefer the kit's @Outputs where they exist (they're already zone-correct). Alternatively, consume CometChatUIKit.loggedInUser$ (an RxJS observable) with the async pipe — observables integrate with change detection cleanly.
5. OnPush change detection
CometChat components are heavy; if your host component uses ChangeDetectionStrategy.OnPush, pass immutable references — reassign selectedUser/selectedGroup (a new object) rather than mutating, so OnPush detects the change. Binding the same mutated object won't trigger a re-render. (This mirrors the React "new reference per update" rule.)
6. SSR / Angular Universal
CometChat is a browser-only SDK (WebSocket + IndexedDB + window). It must not run during server-side rendering. The recommended setup is ng new --ssr false (per the docs) — but if your app already uses @angular/ssr, keep CometChat off the server.
- Don't call
CometChatUIKit.init()/ render<cometchat-*>on the server. Because init lives inmain.ts(§1), gate it on the platform —main.tsruns in both the browser and server entry under SSR:
// src/main.ts (SSR-safe)
import { isPlatformBrowser } from "@angular/common";
import { PLATFORM_ID } from "@angular/core";
// bootstrap once, unconditionally:
function boot() {
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
}
if (typeof window === "undefined") {
boot(); // server render — skip CometChat init entirely
} else {
(CometChatUIKit.init(settings) ?? Promise.resolve()).then(boot).catch((e) => { console.error(e); boot(); });
}
(Inside Angular providers/components, use isPlatformBrowser(inject(PLATFORM_ID)) for the same guard.)
- Lazy-load the chat route (
loadComponent) so the kit's bundle never enters the server build's initial chunk. - With
@angular/ssr, render the chat route client-side only (it's behind the auth guard + browser check above).
7. Anti-patterns
- Don't use NgModule wiring (
@NgModule,AppModule,declarations,forRoot,CUSTOM_ELEMENTS_SCHEMA) — those are v4. v5 is standalone:app.config.tsproviders +imports: []on standalone components. - Don't put
CometChatUIKit.init()inAPP_INITIALIZER— the v5 canonical inits inmain.tsand bootstraps inside the resolved promise.app.config.tscarries providers only. - Don't use a class
CanActivateguard when a functionalCanActivateFnis cleaner (v17+). Match the standalone idiom — noInjectableguard class. - Don't init on the server — gate
main.tsinit on the platform (typeof window/isPlatformBrowser). - Don't forget
zone.run()around view-state mutations inside raw SDK listener callbacks — or useloggedInUser$+asyncpipe. - Don't
loadChildrena chat NgModule — there isn't one;loadComponenta standalone component. - Don't call
login({ uid })— v5 takes a bare UID string (seecometchat-angular-core).
Pointers
cometchat-angular-core— init/login, standalone setup, envcometchat-angular-components— the component catalogcometchat-angular-placement— where to mount chat (route/modal/drawer)cometchat-angular-production— server-minted auth tokens