The root injectStream injector exposes always-on projections
(values, messages, toolCalls, interrupts, error, isLoading,
discovery maps). Anything else — scoped subagent state, message
metadata, the submission queue, raw channels, media — is available
through the companion selector injectors.
Each selector injector opens a ref-counted subscription when the
first component calls it and releases it when the last consumer's
DestroyRef is destroyed. Root calls (no target) are free — they read
the already-mounted root projection directly.
All scoped selectors accept a target argument. Valid targets are:
undefined (or omitted) — the root namespace. Free read.SubagentDiscoverySnapshot — as exposed via
stream.subagents().values().SubgraphDiscoverySnapshot — as exposed via
stream.subgraphs() / stream.subgraphsByNode().{ namespace: string[] } (or a raw string[]) — an explicit
namespace, useful for custom routing.Signal or factory returning any target above — projections
rebind automatically when the target changes.Subscriptions open on mount and close when the last consumer for a
given (channel, namespace) tuple unmounts. Components that don't
render a subagent's content never pay for its wire traffic.
| Selector | Returns | Use for |
|---|---|---|
injectValues(stream, target?) |
Signal<StateType> (root) / Signal<T \| undefined> (scoped) |
Arbitrary state / scoped snapshot. |
injectMessages(stream, target?) |
Signal<BaseMessage[]> |
Message stream, root or scoped. |
injectToolCalls(stream, target?) |
Signal<AssembledToolCall[]> |
Tool-call stream, with per-call status. |
injectMessageMetadata(stream, msgId) |
Signal<{ parentCheckpointId } \| undefined> |
Powers fork / edit flows. See Fork & edit. |
injectSubmissionQueue(stream) |
{ entries, size, cancel, clear } |
Reactive client-side submission queue. See Queue. |
injectExtension(stream, name, target?) |
Signal<T \| undefined> |
Read a named custom:<name> extension. |
injectChannel(stream, channels, target?, options?) |
Signal<Event[]> |
Low-level raw-events escape hatch. |
injectChannelEffect(stream, channels, options) |
void |
Per-event side effects like analytics or logs. |
injectAudio / injectImages / injectVideo / injectFiles |
Signal<AudioMedia[]> / Signal<ImageMedia[]> / Signal<VideoMedia[]> / Signal<FileMedia[]> |
Assembled multimodal streams. See Multimodal. |
injectMediaUrl(handle) |
Signal<string \| undefined> |
Turns a media handle into an <img/audio/video src>. |
import { Component, Input } from "@angular/core";
import {
injectMessages,
injectStream,
injectToolCalls,
injectValues,
type AnyStream,
type SubagentDiscoverySnapshot,
} from "@langchain/angular";
@Component({
standalone: true,
template: `
<app-thread-view [messages]="rootMessages()" />
@for (sub of stream.subagents() | keyvalue; track sub.key) {
<app-subagent-card [stream]="stream" [subagent]="sub.value" />
}
`,
})
export class ChatComponent {
readonly stream = injectStream({
assistantId: "agent",
apiUrl: "/api",
});
readonly rootMessages = injectMessages(this.stream);
readonly rootValues = injectValues(this.stream);
}
@Component({
standalone: true,
selector: "app-subagent-card",
template: `
<section>
<header>{{ subagent.name }} - {{ subagent.status }}</header>
@for (msg of messages(); track msg.id ?? $index) {
<app-bubble [msg]="msg" />
}
</section>
`,
})
export class SubagentCardComponent {
@Input({ required: true }) stream!: AnyStream;
@Input({ required: true }) subagent!: SubagentDiscoverySnapshot;
readonly messages = injectMessages(this.stream, () => this.subagent);
readonly toolCalls = injectToolCalls(this.stream, () => this.subagent);
readonly values = injectValues<ResearcherState>(this.stream, () => this.subagent);
}
When target is a Signal or factory, the projection follows the
current value. This is useful for detail panes that switch between
subagents without recreating the host component:
@Component({
/* ... */
})
export class InspectorComponent {
readonly stream = injectStream();
readonly selectedSubagent = signal<SubagentDiscoverySnapshot | undefined>(undefined);
readonly messages = injectMessages(this.stream, this.selectedSubagent);
readonly values = injectValues(this.stream, this.selectedSubagent);
}
injectMessageMetadataReturns { parentCheckpointId } (and undefined while loading). Use it
to drive fork / edit UIs:
import { Component, Input } from "@angular/core";
import { HumanMessage, type BaseMessage } from "@langchain/core/messages";
import { injectMessageMetadata, type AnyStream } from "@langchain/angular";
@Component({
template: `
<button [disabled]="!metadata()?.parentCheckpointId" (click)="editFromHere()">
Edit from here
</button>
`,
})
export class EditButtonComponent {
@Input({ required: true }) stream!: AnyStream;
@Input({ required: true }) message!: BaseMessage;
readonly metadata = injectMessageMetadata(this.stream, () => this.message.id);
editFromHere() {
const forkFrom = this.metadata()?.parentCheckpointId;
if (!forkFrom) return;
void this.stream.submit({ messages: [new HumanMessage("...revised prompt...")] }, { forkFrom });
}
}
See Fork & edit from a checkpoint for the full flow.
injectChannelEscape hatch to the raw protocol event stream. Subscribe to one or more channels and get the buffered events as a signal:
const events = injectChannel(stream, ["values", "updates"]);
Pass target (subagent / subgraph / { namespace }) to scope. Useful
for bespoke reducers that can't be expressed through injectValues /
injectMessages.
injectExtensionRead a single custom extension (wire-level custom:<name> channel) as
a reactive snapshot:
const telemetry = injectExtension<Telemetry>(stream, "telemetry");
Runnable example: The
a2uiapp in the streaming cookbook drives a generative UI withinjectExtension(stream, "a2ui"); thestreamingpackage'scustom-transformer:*scripts show the server side.
injectChannelEffectinjectChannel is for events you render. When you instead want to
react to each event — fire analytics, write a log, increment a metric —
use injectChannelEffect. It invokes onEvent once per event and
returns nothing, so it does not trigger template updates:
import { Component } from "@angular/core";
import { injectChannelEffect, injectStream } from "@langchain/angular";
@Component({
/* ... */
})
export class AnalyticsComponent {
readonly stream = injectStream();
constructor() {
injectChannelEffect(this.stream, ["lifecycle", "tools"], {
replay: false,
onEvent: (event) => sendAnalytics(event),
onError: (error) => logger.error(error),
});
}
}
channels, target, and enabled accept signals, so effects can
rebind without rebuilding the component. replay defaults to false
(live-only); buffered events are not re-delivered unless you opt in.
injectProjection — building your own selectorinjectProjection is the low-level primitive
every built-in selector is composed from. Reach for it only when you
need a custom projection that the built-in selectors don't express. It
acquires a ref-counted projection from the stream's channel registry and
subscribes to its store through Angular signals:
import { injectProjection, STREAM_CONTROLLER } from "@langchain/angular";
import { valuesProjection } from "@langchain/langgraph-sdk/stream";
function injectScoredValues<T>(stream: AnyStream, namespace: readonly string[]) {
const registry = stream[STREAM_CONTROLLER].registry;
return injectProjection<T | undefined>(
registry,
() => valuesProjection<T>(namespace, "messages"),
`values|${namespace.join(">")}`,
undefined,
);
}
The first read returns initialValue. Subsequent reads use the acquired
store. When the last consumer of a given key unmounts, the underlying
server subscription is released automatically. Most apps never need
this — prefer injectValues / injectMessages / injectToolCalls /
injectChannel.
Subscribe to a scoped values stream — the most recent state
Subscribe to a scoped messages stream.
Subscribe to a scoped tools (tool-call) stream. Same target and
Read metadata recorded for a specific message id — today exposes
Subscribe to a custom:<name> stream extension — the most recent
Side-effect counterpart to injectChannel. Instead of
Angular-side primitive that composes ChannelRegistry.acquire