Page actions

Try without a backend

Render a real <chat> in your Angular app with no server, no LLM and no account. provideFakeAgent() streams a canned reply in-process through the same components you will ship. When the UI looks right, swap the provider for a real adapter.

Note: What you need

An Angular 20–22 application. Nothing else.

1
Install
npm install @threadplane/chat @threadplane/langgraph @langchain/core @langchain/langgraph-sdk marked

provideFakeAgent() ships inside the adapter packages, so install the adapter you expect to use later. The LangChain packages are peers of @threadplane/langgraph; marked renders assistant markdown.

Tip: Bundle budget

A default Angular application caps the initial bundle at 1 MB, and a chat UI plus the LangGraph SDK exceeds it. Raise budgets[].maximumError for the initial bundle in angular.json if ng build reports a budget error.

2
Provide the fake agent
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideFakeAgent } from '@threadplane/langgraph';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideFakeAgent({ tokens: ['Hello', ' from', ' Threadplane.'] }),
  ],
};

FakeAgentConfig accepts tokens, reasoningTokens and delayMs. Leave them out and the fake agent streams a default reply.

3
Render the chat
// app.component.ts
import { Component } from '@angular/core';
import { injectAgent } from '@threadplane/langgraph';
import { ChatComponent } from '@threadplane/chat';
 
@Component({
  selector: 'app-root',
  imports: [ChatComponent],
  template: `<chat [agent]="agent" />`,
})
export class AppComponent {
  protected readonly agent = injectAgent();
}

Run ng serve, type anything, and watch the reply stream in.

4
Test it
// app.component.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideFakeAgent } from '@threadplane/langgraph';
import { AppComponent } from './app.component';
 
it('streams the fake reply', async () => {
  TestBed.configureTestingModule({
    imports: [AppComponent],
    providers: [provideFakeAgent({ tokens: ['Hello', ' from', ' Threadplane.'], delayMs: 0 })],
  });
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const textarea = fixture.nativeElement.querySelector('textarea');
  textarea.value = 'hi';
  textarea.dispatchEvent(new Event('input'));
  fixture.detectChanges();
  fixture.nativeElement.querySelector('button[aria-label="Send message"]').click();
  await fixture.whenStable();
  fixture.detectChanges();
  expect(fixture.nativeElement.textContent).toContain('Hello from Threadplane.');
});

The detectChanges() call after the input event matters: the send button stays disabled until change detection runs, so a click before it does nothing.

Connect a real adapter

Replace provideFakeAgent(...) with one line and keep every component as it is:

  • LangGraph: provideAgent({ apiUrl: 'http://localhost:2024', assistantId: 'agent' })LangGraph quickstart
  • AG-UI: provideAgent({ url: 'http://localhost:8000/agent' }) from @threadplane/ag-uiAG-UI quickstart

Not every backend emits every capability; see Choosing an adapter.

Looking for something specific?