showcase/shell-docs/src/content/reference/angular/functions/registerRenderToolCall.mdx
registerRenderToolCall registers a renderer for a tool call. It does not run a handler. It tells CopilotKit which standalone Angular component to render whenever a tool call with a matching name appears in the conversation. Use it to visualize tool calls that execute somewhere other than the browser, for example a server-side tool or an agentic (long-running) tool, where you only want to show progress and results in the chat.
You call registerRenderToolCall inside an Angular injection context (a component or service constructor, or a field initializer). The renderer registers immediately, and CopilotKit removes it automatically when the owning component or service is destroyed.
This is the Angular equivalent of React's useRenderTool() / useRenderToolCall(). If you want to both run a browser handler and render UI, use registerFrontendTool with its component field instead.
import { registerRenderToolCall } from "@copilotkit/angular";
function registerRenderToolCall<Args extends Record<string, unknown>>(
renderToolCall: RenderToolCallConfig<Args>,
): void;
registerRenderToolCall returns void. It registers the renderer as a side effect and wires up automatic cleanup.
Your component implements the ToolRenderer<Args> interface. CopilotKit binds the inputs by name, so declare them with Angular's input():
import { Signal } from "@angular/core";
import { AbstractAgent } from "@ag-ui/client";
interface ToolRenderer<Args extends Record<string, unknown>> {
// Required: the current state of the tool call.
toolCall: Signal<AngularToolCall<Args>>;
// Bound only when the config sets passAgent: true.
agent?: Signal<AbstractAgent | undefined>;
}
toolCall() returns a discriminated union keyed on status. The shape of args and result depends on the status:
type AngularToolCall<Args extends Record<string, unknown>> =
| {
name?: string;
args: Partial<Args>; // streaming, may be incomplete
status: "in-progress";
result: undefined;
}
| {
name?: string;
args: Args; // fully parsed
status: "executing";
result: undefined;
}
| {
name?: string;
args: Args; // fully parsed
status: "complete";
result: string; // the tool result, as a string
};
in-progress means the arguments are still streaming in, so args is Partial<Args> and result is undefined.executing means the arguments have fully arrived and the tool is running, so args is the complete Args and result is still undefined.complete means the tool finished, so args is complete and result holds the string result.This renderer shows a spinner while the tool runs and then renders the result. It narrows on status so the template only reads result once it is available.
import { Component, input } from "@angular/core";
import { AngularToolCall, ToolRenderer } from "@copilotkit/angular";
type SearchArgs = { query: string };
@Component({
selector: "app-search-tool-view",
standalone: true,
template: `
@let call = toolCall();
@switch (call.status) {
@case ("in-progress") {
<div class="text-sm opacity-70">Preparing search...</div>
}
@case ("executing") {
<div class="text-sm opacity-70">Searching for "{{ call.args.query }}"...</div>
}
@case ("complete") {
<pre class="rounded border p-3 text-sm">{{ call.result }}</pre>
}
}
`,
})
export class SearchToolViewComponent implements ToolRenderer<SearchArgs> {
readonly toolCall = input.required<AngularToolCall<SearchArgs>>();
}
Register it for the tool name your agent emits:
import { Component } from "@angular/core";
import { z } from "zod";
import { registerRenderToolCall } from "@copilotkit/angular";
import { SearchToolViewComponent } from "./search-tool-view.component";
@Component({
selector: "app-chat",
standalone: true,
template: ``,
})
export class ChatComponent {
constructor() {
registerRenderToolCall({
name: "searchDocuments",
args: z.object({ query: z.string() }),
component: SearchToolViewComponent,
});
}
}
Set passAgent: true to have CopilotKit bind the agent signal input as well. Declare it on the component as an optional input().
import { Component, input } from "@angular/core";
import { AbstractAgent } from "@ag-ui/client";
import { AngularToolCall, ToolRenderer } from "@copilotkit/angular";
type HandoffArgs = { target: string };
@Component({
selector: "app-handoff-tool-view",
standalone: true,
template: `<div>Handing off to {{ toolCall().args.target }}</div>`,
})
export class HandoffToolViewComponent implements ToolRenderer<HandoffArgs> {
readonly toolCall = input.required<AngularToolCall<HandoffArgs>>();
readonly agent = input<AbstractAgent | undefined>();
}
registerRenderToolCall({
name: "handoff",
args: z.object({ target: z.string() }),
component: HandoffToolViewComponent,
passAgent: true,
});