DocsChatComponentsChatMessageListComponent

ChatMessageListComponent

ChatMessageListComponent is the core primitive for rendering chat messages. It iterates over the messages from an Agent and renders each one using a matching MessageTemplateDirective. This gives you full control over how each message type is displayed.

Selector: chat-message-list

Import:

import {
  ChatMessageListComponent,
  MessageTemplateDirective,
  getMessageType,
} from '@ngaf/chat';

How It Works

  1. The component reads agent().messages() to get the current message list
  2. For each message, it calls getMessageType() to determine the template type
  3. It finds the matching MessageTemplateDirective among its content children
  4. It renders the message using ngTemplateOutlet with the message as the implicit context

Basic Usage

<chat-message-list [agent]="chatAgent">
  <ng-template chatMessageTemplate="human" let-message>
    <div class="user-bubble">{{ message.content }}</div>
  </ng-template>
 
  <ng-template chatMessageTemplate="ai" let-message>
    <div class="ai-message">{{ message.content }}</div>
  </ng-template>
 
  <ng-template chatMessageTemplate="tool" let-message>
    <pre>{{ message.content }}</pre>
  </ng-template>
 
  <ng-template chatMessageTemplate="system" let-message>
    <em>{{ message.content }}</em>
  </ng-template>
</chat-message-list>

API

Inputs

InputTypeDefaultDescription
agentAgentRequiredThe agent providing streaming state

Content Children

The component queries all MessageTemplateDirective instances declared as content children. Each directive declares which message type it handles.

Template Context

Each template receives:

VariableTypeDescription
$implicit (via let-message)MessageThe runtime-neutral chat message
indexnumberThe index of the message in the array
<ng-template chatMessageTemplate="ai" let-message let-idx="index">
  <div>Message #{{ idx }}: {{ message.content }}</div>
</ng-template>

MessageTemplateDirective

The MessageTemplateDirective is a structural directive applied to ng-template elements. It declares which message type the template should handle.

Selector: ng-template[chatMessageTemplate]

import { MessageTemplateDirective } from '@ngaf/chat';

Input

InputTypeDescription
chatMessageTemplateMessageTemplateTypeThe message type this template handles

MessageTemplateType

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

getMessageType()

The getMessageType() function maps a runtime-neutral Message role to a MessageTemplateType.

import { getMessageType } from '@ngaf/chat';
 
const type = getMessageType(message); // 'human' | 'ai' | 'tool' | 'system' | 'function'

Mapping logic:

Runtime-neutral roleReturns
'user''human'
'assistant''ai'
'tool''tool'
'system''system'
Any other value'ai' (default fallback)

Working with Message Content

Runtime-neutral messages have a content property that can be either a string or a structured array. The library exports a messageContent() utility (used internally by compositions) that handles both cases:

// If content is a string, returns it directly
// If content is structured, serializes to JSON
function messageContent(message: Message): string

For custom templates, you can access message.content directly and handle the type yourself:

<ng-template chatMessageTemplate="ai" let-message>
  @if (isString(message.content)) {
    <div [innerHTML]="renderMd(message.content)"></div>
  } @else {
    <pre>{{ message.content | json }}</pre>
  }
</ng-template>

Full Example

import { Component, inject, ChangeDetectionStrategy, signal } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { agent } from '@ngaf/langgraph';
import {
  ChatMessageListComponent,
  MessageTemplateDirective,
  renderMarkdown,
} from '@ngaf/chat';
 
@Component({
  selector: 'app-messages-demo',
  standalone: true,
  imports: [ChatMessageListComponent, MessageTemplateDirective],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <chat-message-list [agent]="chatAgent">
      <ng-template chatMessageTemplate="human" let-message>
        <div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
          <div style="background: var(--ngaf-chat-primary); color: var(--ngaf-chat-on-primary); border-radius: var(--ngaf-chat-radius-bubble); padding: 0.5rem 1rem; max-width: 70%;">
            {{ message.content }}
          </div>
        </div>
      </ng-template>
 
      <ng-template chatMessageTemplate="ai" let-message>
        <div style="display: flex; gap: 0.75rem; margin-bottom: 1rem;">
          <div style="flex: 1;" [innerHTML]="renderMd(message.content)"></div>
        </div>
      </ng-template>
 
      <ng-template chatMessageTemplate="tool" let-message>
        <div style="background: var(--ngaf-chat-surface-alt); border-radius: var(--ngaf-chat-radius-card); padding: 0.75rem; font-family: var(--ngaf-chat-font-mono); font-size: var(--ngaf-chat-font-size-sm); margin-bottom: 1rem;">
          {{ message.content }}
        </div>
      </ng-template>
 
      <ng-template chatMessageTemplate="system" let-message>
        <div style="text-align: center; color: var(--ngaf-chat-text-muted); font-size: var(--ngaf-chat-font-size-xs); font-style: italic; margin-bottom: 1rem;">
          {{ message.content }}
        </div>
      </ng-template>
    </chat-message-list>
  `,
})
export class MessagesDemoComponent {
  private sanitizer = inject(DomSanitizer);
 
  chatAgent = agent({
    assistantId: 'chat_agent',
    threadId: signal(null),
  });
 
  renderMd(content: string | unknown) {
    if (typeof content !== 'string') return '';
    return renderMarkdown(content, this.sanitizer);
  }
}