Primitives vs Compositions

@ngaf/chat gives you two levels of UI.

Use compositions when the default product shape is close to what you need. Use primitives when the layout, interaction model, or rendering policy is part of your application.

This matters because chat UI gets complicated quickly. Message streaming, input state, tool calls, reasoning, interrupts, subagents, auto-scroll, markdown, and generative UI all need to agree on the same Agent. The fastest path is usually to start with a composition and drop down only where the product needs control.

#Compositions

The main composition is <chat>.

import { ChatComponent } from '@ngaf/chat';
<chat [agent]="agent" />

ChatComponent composes the common pieces:

  • chat-message-list for messages.
  • chat-input for text entry and stop behavior.
  • Markdown rendering for assistant content.
  • Reasoning display.
  • Tool call cards.
  • Subagent display.
  • A2UI and json-render surfaces when views are provided.
  • Optional thread sidebar.
  • Auto-scroll and scroll-to-bottom behavior.

Use <chat> when you want the library to own the chat shell.

<chat
  [agent]="agent"
  [views]="views"
  [handlers]="handlers"
  [threads]="threads()"
  [activeThreadId]="activeThreadId()"
  (threadSelected)="selectThread($event)"
/>

<chat-debug> is also a composition. It is built for development and inspection, not end-user chat layout, and ships from the debug-only secondary entry point.

import { ChatDebugComponent } from '@ngaf/chat/debug';
<chat [agent]="agent" />
<chat-debug [agent]="agent" />

Use chat-debug when you need timeline and state inspection while building. Do not treat it as a customer-facing control surface unless you intentionally design around that.

If you use <chat-sidenav>, pass [agent]="agent" and [debug]="true" to get the built-in footer launcher instead of adding a separate floating debug button.

#Primitives

Primitives are standalone Angular components and directives. They let you assemble your own shell while keeping the tested chat pieces.

The core primitive set usually starts with chat-message-list and chat-input:

import {
  ChatMessageListComponent,
  MessageTemplateDirective,
  ChatInputComponent,
} from '@ngaf/chat';
<section class="support-chat">
  <header>Support</header>
 
  <chat-message-list [agent]="agent">
    <ng-template chatMessageTemplate="human" let-message>
      <app-user-message [message]="message" />
    </ng-template>
 
    <ng-template chatMessageTemplate="ai" let-message>
      <app-assistant-message [message]="message" />
    </ng-template>
 
    <ng-template chatMessageTemplate="tool" let-message>
      <app-tool-result [message]="message" />
    </ng-template>
 
    <ng-template chatMessageTemplate="system" let-message>
      <app-system-note [message]="message" />
    </ng-template>
  </chat-message-list>
 
  <chat-input [agent]="agent" placeholder="Ask a question" />
</section>

Use primitives when you need:

  • A custom shell, sidebar, header, footer, or mobile layout.
  • Product-specific message rendering.
  • Custom markdown policy.
  • A different input placement or composer.
  • Separate handling for tool calls, citations, interrupts, or subagents.
  • A chat view embedded inside an existing operational screen.

#Choosing The Level

NeedUse
A complete chat UI quickly<chat>
Default chat plus projected tool-call templates<chat>
Built-in thread list and auto-scroll<chat>
Development inspection<chat-debug>
Custom message layoutchat-message-list
Custom composer around the same agentchat-input
Full product-specific shellprimitives
You are building a design system wrapperprimitives

The practical tradeoff is ownership. A composition owns more behavior for you. A primitive gives you more control and more responsibility.

#Template Role Names

Runtime messages use these roles:

type Role = 'user' | 'assistant' | 'system' | 'tool';

Message templates use UI names:

type MessageTemplateType = 'human' | 'ai' | 'tool' | 'system' | 'function';

The mapping is:

Message rolechatMessageTemplate
userhuman
assistantai
tooltool
systemsystem

function exists as a template type for compatibility with older function-message vocabulary. The current runtime-neutral Role type does not include function, so do not build new adapters around a separate function role. Normalize function output into tool messages or adapter-specific extra data.

#Gotchas

Give <chat> a parent with height. The composition uses flex layout and height: 100%; without a constrained parent, it can collapse.

Do not pass a raw backend client to chat primitives. They need the Agent contract from @ngaf/chat, not a LangGraph SDK client or AG-UI AbstractAgent.

Do not mix two agents in one shell unless you mean it. chat-message-list, chat-input, tool-call UI, and debug UI should usually point at the same Agent instance.

If you use primitives, you own the behaviors the composition normally provides: auto-scroll, empty state, error display, reasoning placement, tool-call placement, subagent placement, and interrupt display.

If you use <chat> and project custom pieces, stay inside the supported projection points. For example, project chatToolCallTemplate for tool-call rendering and [chatInputModelSelect] content for the model picker. If the whole structure needs to move, switch to primitives.

#A Conservative Path

Start with <chat> while wiring the runtime.

Move individual surfaces to templates when the product needs customization.

Move to primitives when layout becomes the requirement.

That keeps early integration focused on the agent contract and delays UI ownership until there is a concrete reason to take it on.