cometchat-angular-placement — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-angular-placement (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Ground truth:@cometchat/[email protected]standalone components +docs/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.
The five canonical placement patterns for chat in an Angular app, each as v5 standalone-component composition. Read cometchat-angular-core (setup) + cometchat-angular-components (catalog) first.
v5 has no composite components. There is no <cometchat-conversations-with-messages> — every layout composes the individual standalone components (<cometchat-conversations> + <cometchat-message-header> / -message-list> / -message-composer>). Import each component class into the host standalone component's imports: []. No NgModule, no CUSTOM_ELEMENTS_SCHEMA.
| User intent | Recommended placement | Experience |
|---|---|---|
| Messaging app (WhatsApp / Telegram style) | Route-based — /conversations → /messages/:uid | Full-page chat |
| SaaS / marketplace with chat as a feature | Sidebar — persistent panel alongside main content | Split-pane |
| Support app or focused 1-to-1 | Route-based (single thread) — straight into one chat | Single thread |
| Full messaging hub with calls / users / groups | Tab-based — Chats / Users / Groups / Calls tabs | Tabbed messenger |
| Occasional chat overlay from a non-chat screen | Modal/Dialog — Angular Material MatDialog / CDK overlay | Modal |
| Chat embedded inside an existing page section | Embedded — components inside a parent layout | Inline |
1. Route-based (full page) 2. Sidebar (split-pane)
┌───────────────────────────┐ ┌────────────┬────────────────┐
│ ← Hiking Group ⋮ │ │ Convos │ ← Group ⋮ │
├───────────────────────────┤ │ ───────────│ ───────────────│
│ (messages) │ │ Hiking │ (messages) │
├───────────────────────────┤ │ Alice │ ───────────────│
│ + Type a message… ▶ │ │ Bob │ Type message ▶ │
└───────────────────────────┘ └────────────┴────────────────┘
3. Modal/Dialog 4. Tab-based 5. Embedded
┌──────────────────┐ ┌────────────────────┐ ┌──────────────────────┐
│ Chat w/ Alice ✕ │ │ Chats Users Groups │ │ Product details │
├──────────────────┤ ├────────────────────┤ ├──────────────────────┤
│ (messages) │ │ (active tab) │ │ Chat with seller │
│ Type message ▶ │ │ │ │ [header/list/composer]│
└──────────────────┘ └────────────────────┘ └──────────────────────┘All five compose the same primitives — only the container/routing differs.
Most placements reuse one composed chat surface. Define it once as a standalone component:
// chat-pane.component.ts
import { Component, Input } from "@angular/core";
import {
CometChatMessageHeaderComponent,
CometChatMessageListComponent,
CometChatMessageComposerComponent,
} from "@cometchat/chat-uikit-angular";
import { CometChat } from "@cometchat/chat-sdk-javascript";
@Component({
selector: "app-chat-pane",
standalone: true,
imports: [
CometChatMessageHeaderComponent,
CometChatMessageListComponent,
CometChatMessageComposerComponent,
],
template: `
<ng-container *ngIf="user || group; else empty">
<cometchat-message-header [user]="user" [group]="group"></cometchat-message-header>
<cometchat-message-list [user]="user" [group]="group"></cometchat-message-list>
<cometchat-message-composer [user]="user" [group]="group"></cometchat-message-composer>
</ng-container>
<ng-template #empty><div class="empty">Select a conversation</div></ng-template>`,
styles: [`
:host { display: flex; flex-direction: column; height: 100%; }
cometchat-message-header, cometchat-message-composer { flex-shrink: 0; }
cometchat-message-list { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
`],
})
export class ChatPaneComponent {
@Input() user?: CometChat.User;
@Input() group?: CometChat.Group;
}<cometchat-message-header> / -message-list> / -message-composer> each expose optional @Input() user? and @Input() group?. They also inject the kit's ChatStateService (exported from @cometchat/chat-uikit-angular) and auto-subscribe to its activeUser() / activeGroup() signals when no input is bound. So:
<cometchat-conversations> has no `(itemClick)` handler bound, clicking a row auto-calls ChatStateService.setActiveConversation(conv), which the message components react to. You only read state for layout gating, e.g. @if (chatState.activeUser() || chatState.activeGroup()). To drive it yourself (a thread route, a deep link), call chatState.setActiveUser(user) / setActiveGroup(group) — these are mutually exclusive (setting one clears the other).app-chat-pane above). Use this when you bind your own (itemClick) handler and route/own the selection — see the next note.The app-chat-pane above uses props mode so it's reusable across routes/modals/embeds. For a single-host sidebar that matches the docs verbatim, drop the inputs and let ChatStateService wire it.
Load-bearing kit behavior:<cometchat-conversations>only auto-setsChatStateServicewhen `(itemClick)` is not observed. The source is literallyif (this.itemClick.observed) { this.itemClick.emit(conv); } else { this.chatStateService.setActiveConversation(conv); }. The moment you bind `(itemClick)`, the auto-wiring stops — you must then drive the view yourself (props,chatState.setActive*, orrouter.navigate).<cometchat-users>/<cometchat-groups>behave the same way withsetActiveUser/setActiveGroup.
The kit's <cometchat-*> elements are display: inline by default, so the list won't flex or scroll without the host contract above. Two things are load-bearing: (1) the host is a flex column with height: 100%, and (2) each custom element is itself made a flex column — cometchat-message-list needs flex: 1; min-height: 0; overflow: hidden (the min-height: 0 is what lets it shrink and scroll inside the flex parent; omit it and the list overflows instead of scrolling). The app's index.html/global styles must also set html, body { height: 100% } so the height: 100% chain has a root to resolve against.
Two routes: a conversation list and a thread. Lazy-load both as standalone components.
// app.routes.ts
export const routes: Routes = [
{ path: "conversations", loadComponent: () =>
import("./conversations-page.component").then(m => m.ConversationsPageComponent) },
{ path: "messages/:uid", loadComponent: () =>
import("./messages-page.component").then(m => m.MessagesPageComponent) },
];// conversations-page.component.ts
@Component({
standalone: true,
imports: [CometChatConversationsComponent],
template: `<cometchat-conversations (itemClick)="open($event)"></cometchat-conversations>`,
})
export class ConversationsPageComponent {
constructor(private router: Router) {}
open(c: CometChat.Conversation) {
const e = c.getConversationWith();
if (e instanceof CometChat.User) this.router.navigate(["/messages", e.getUid()]);
}
}messages-page.component.ts reads :uid, resolves the CometChat.User (via CometChat.getUser(uid)), and renders <app-chat-pane [user]="user">.
Binding (itemClick) here is deliberate: it suppresses the kit's auto-setActiveConversation (you want a navigation, not an in-place swap) and hands you the conversation to route with. The thread page then uses props mode (or calls chatState.setActiveUser) since the list and the thread live on different routes.
One standalone component, conversations on the left, the chat surface on the right. This is the layout the docs and the kit's own sample app (cometchat-home → cometchat-selector + cometchat-messages) ship — both use state-service mode.
Recommended — state-service auto-wiring (docs/sample-app verbatim). No (itemClick), no inputs, no manual reassignment. <cometchat-conversations> auto-sets ChatStateService on click and the message components auto-subscribe. Import inject from @angular/core and ChatStateService + the four component classes from @cometchat/chat-uikit-angular:
@Component({
selector: "app-chat-sidebar",
standalone: true,
imports: [
CometChatConversationsComponent,
CometChatMessageHeaderComponent,
CometChatMessageListComponent,
CometChatMessageComposerComponent,
],
template: `
<div class="split">
<aside><cometchat-conversations></cometchat-conversations></aside>
<main>
@if (chatState.activeUser() || chatState.activeGroup()) {
<cometchat-message-header></cometchat-message-header>
<cometchat-message-list></cometchat-message-list>
<cometchat-message-composer></cometchat-message-composer>
} @else {
<div class="empty">Select a conversation</div>
}
</main>
</div>`,
styles: [`
.split { display: flex; height: 100%; }
aside { width: 320px; flex-shrink: 0; border-right: 1px solid var(--cometchat-border-color-light); overflow: hidden; display: flex; flex-direction: column; }
aside cometchat-conversations { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
main cometchat-message-header, main cometchat-message-composer { flex-shrink: 0; }
main cometchat-message-list { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
`],
})
export class ChatSidebarComponent {
chatState = inject(ChatStateService);
}Alternative — props mode (explicit control). Bind (itemClick) and drive app-chat-pane yourself. Binding (itemClick) suppresses the auto-wiring (see the load-bearing note above), so you own the selection:
@Component({
selector: "app-chat-sidebar",
standalone: true,
imports: [CometChatConversationsComponent, ChatPaneComponent],
template: `
<div class="split">
<aside><cometchat-conversations (itemClick)="select($event)"></cometchat-conversations></aside>
<main><app-chat-pane [user]="activeUser" [group]="activeGroup"></app-chat-pane></main>
</div>`,
styles: [`.split{display:flex;height:100%} aside{width:320px;flex-shrink:0;border-right:1px solid var(--cometchat-border-color-light)} main{flex:1;min-width:0}`],
})
export class ChatSidebarComponent {
activeUser?: CometChat.User; activeGroup?: CometChat.Group;
select(c: CometChat.Conversation) {
const e = c.getConversationWith();
// reassign (new ref) so OnPush hosts update
if (e instanceof CometChat.User) { this.activeUser = e; this.activeGroup = undefined; }
if (e instanceof CometChat.Group) { this.activeGroup = e; this.activeUser = undefined; }
}
}Render the composed chat pane inside a MatDialog. Never a composite — there isn't one, and a 3-panel composite wouldn't fit a modal anyway.
// open from any component:
this.dialog.open(ChatDialogComponent, { width: "420px", height: "640px", data: { user } });
// chat-dialog.component.ts
@Component({
standalone: true,
imports: [ChatPaneComponent],
template: `<app-chat-pane [user]="data.user"></app-chat-pane>`,
})
export class ChatDialogComponent {
constructor(@Inject(MAT_DIALOG_DATA) public data: { user: CometChat.User }) {}
}(For an inbox-in-modal, drop <cometchat-conversations> above the pane in a two-pane dialog body.)
A standalone shell with Material tabs (or your own) over Conversations / Users / Groups / Call Logs.
@Component({
standalone: true,
imports: [MatTabsModule, CometChatConversationsComponent, CometChatUsersComponent,
CometChatGroupsComponent, CometChatCallLogsComponent, ChatPaneComponent],
template: `
<mat-tab-group>
<mat-tab label="Chats"><div class="tab-pane"><cometchat-conversations (itemClick)="select($event)"></cometchat-conversations></div></mat-tab>
<mat-tab label="Users"><div class="tab-pane"><cometchat-users (itemClick)="selectUser($event)"></cometchat-users></div></mat-tab>
<mat-tab label="Groups"><div class="tab-pane"><cometchat-groups (itemClick)="selectGroup($event)"></cometchat-groups></div></mat-tab>
<mat-tab label="Calls"><div class="tab-pane"><cometchat-call-logs></cometchat-call-logs></div></mat-tab>
</mat-tab-group>`,
styles: [`
:host { display: flex; flex-direction: column; height: 100%; }
mat-tab-group { flex: 1; min-height: 0; }
.tab-pane { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.tab-pane > * { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
`],
})
export class ChatTabsComponent { /* select handlers route to a thread or open the pane */ }MatTabsModule is a standalone-importable Angular Material module — add it to imports: [] alongside the CometChat components.
Selection wiring is the same trade-off as the sidebar. If you want the kit to auto-route a click into the active conversation (the docs' tab example does this), drop the `(itemClick)` handlers — <cometchat-conversations> auto-calls setActiveConversation and <cometchat-users> auto-calls setActiveUser, then mount the message components in a side/center pane gated on chatState.activeUser()/activeGroup(). Bind (itemClick) only when you want to route to a separate page or otherwise own the selection.
The list components inside each <mat-tab> need the same flex-host treatment as the chat pane — a Material tab body has no implicit height, so without a flex .tab-pane wrapper (and the elements made flex: 1; min-height: 0; overflow: hidden) the inline <cometchat-*> lists collapse to zero height and won't scroll.
Drop <app-chat-pane> (or the raw header/list/composer) inside an existing page, scoped to a known user/group.
@Component({
standalone: true,
imports: [ChatPaneComponent],
template: `
<section class="product">…product details…</section>
<section class="seller-chat" style="height:480px">
<h3>Chat with seller</h3>
<app-chat-pane [user]="seller"></app-chat-pane>
</section>`,
})
export class ProductPageComponent { seller!: CometChat.User; }Give the embedded container an explicit height — the message list fills its parent.
<cometchat-conversations-with-messages> / -users-with-messages> / -groups-with-messages> do not exist — compose <cometchat-conversations> + the message components. (If you've seen these in v4 docs, they were removed.)imports: [].[user]/[group], no (itemClick) — <cometchat-conversations>/-users>/-groups> auto-call ChatStateService.setActive* and the message components auto-subscribe; gate layout on chatState.activeUser()/activeGroup(). Or (b) props mode: pass user XOR group on header/list/composer (reassign a new reference on change so OnPush hosts re-render). Binding `(itemClick)` suppresses the auto-wiring (kit source: itemClick.observed ? emit : setActiveConversation) — once bound, you own selection via props, chatState.setActive*, or routing.<cometchat-*> elements are display: inline by default — they will not flex or scroll on their own. The host must be a flex column with a resolvable height: 100% (so set html, body { height: 100% } globally), and each element must itself be display: flex; flex-direction: column. cometchat-message-list needs flex: 1; min-height: 0; overflow: hidden — without min-height: 0 it overflows the viewport instead of scrolling; header/composer take flex-shrink: 0. With no height or no element-flex, the list collapses or fails to scroll.loadComponent so the kit bundle isn't in the initial chunk.--cometchat-*) — see cometchat-angular-theming, not a theme service.cometchat-angular-core — setup, init, login, ChatStateService lifecyclecometchat-angular-components — component catalog + bindings (@Input user/group, itemClick)cometchat-angular-patterns — routing, guards, lazy loading, NgZone, SSRcometchat-angular-theming — CSS-variable theming~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.