Angular Signals
Angular Signals are the reactive primitive that powers agent(). If you're coming from a Python AI/agent background and wondering how Angular handles real-time streaming data, this page is your guide. Every property on a LangGraphAgent is a Signal, which means your templates update automatically as tokens arrive — no manual subscriptions, no async pipes, no RxJS boilerplate.
Think of Signals like a Python property with built-in change notification. When the value changes, every consumer — templates, computed values, effects — re-evaluates automatically. If you've used Pydantic models with validators that react to field changes, Signals are the Angular equivalent but deeply integrated into the rendering engine.
What Are Angular Signals?
A Signal is a reactive value container introduced in Angular 16+. You create one, read it by calling it like a function, and Angular tracks which templates and computations depend on it.
The key insight: Angular knows which Signals a template reads. When those Signals change, Angular re-renders only the affected parts of the DOM. No diffing the entire tree, no zone.js overhead.
How agent Uses Signals Internally
Under the hood, agent() receives Server-Sent Events (SSE) over HTTP and feeds them into RxJS BehaviorSubjects. It then converts those BehaviorSubjects into Angular Signals using toSignal(). This is the bridge between the async streaming world and Angular's synchronous reactivity model.
The BehaviorSubject-to-Signal conversion means you get the best of both worlds: RxJS handles the async SSE transport (reconnection, backpressure, error recovery), while Signals handle the synchronous UI reactivity (change detection, template binding, computed derivations). You only interact with the Signal side.
The Streaming Lifecycle as Signals
Every agent() instance moves through a lifecycle: idle, running while work is in flight, then back to idle when the stream completes (or error when it fails). The status() Signal reflects each transition in real time, while isLoading() is the convenience signal for loading UI.
The resource has been created but no request has been submitted yet. All Signals hold their initial values.
After calling submit(), the status transitions to 'running'. The SSE connection is open and the agent is processing.
As the agent generates tokens, the messages() Signal updates with each chunk. The status remains 'running' throughout.
The agent has finished. All tokens have arrived. The status transitions back to 'idle'.
If the agent fails or the connection drops, the status transitions to 'error' and the error() Signal contains the failure details.
Composing Derived State with computed()
computed() lets you derive new Signals from agent Signals. These derived Signals update automatically whenever their dependencies change — during streaming, that means every time a new token arrives.
A computed() only re-evaluates when one of its dependencies actually changes, and it caches the result. If chat.messages() emits the same reference, downstream computeds skip their work entirely. This matters for high-frequency streaming where tokens arrive rapidly.
Side Effects with effect()
Use effect() when a Signal change should trigger work that lives outside the template — logging, analytics, scrolling, persisting state. Effects run in the injection context and are automatically cleaned up when the component is destroyed.
Writing to a Signal inside an effect() can create infinite loops. If you need to transform one Signal into another, use computed() instead. Reserve effect() for side effects that leave the reactive graph — DOM manipulation, logging, analytics, network calls.
Template Patterns
Angular's new control flow syntax (@if, @for, @switch) works naturally with Signals. Here's a complete chat template that handles every lifecycle state.
OnPush Change Detection
Every component using agent() should use ChangeDetectionStrategy.OnPush. Here's why it works and why it's efficient.
With the default change detection strategy, Angular checks every component in the tree on every browser event — clicks, timers, HTTP responses. For a streaming agent emitting dozens of tokens per second, that means hundreds of unnecessary checks across your entire app.
With OnPush, Angular only checks a component when:
- An
@Input()reference changes - An event fires inside the component's template
- A Signal that the template reads changes
Since agent() exposes Signals, condition 3 handles everything. When a new token arrives and messages() updates, Angular marks only the components reading that Signal for check — not the entire tree.
With older Observable-based patterns, you had to call ChangeDetectorRef.markForCheck() or use the async pipe to trigger OnPush updates. Signals do this automatically. When a Signal's value changes, Angular's internal notification system marks the component dirty — zero manual intervention.
Python Agent to Angular Signals
The real power of agent() is how it pairs a Python LangGraph agent with Angular Signals. The agent defines the logic; Signals surface the results in real time.
When the Python agent calls search_knowledge_base, the tool call streams to Angular as a message. When the tool returns, the result streams as another message. The agent's final response streams token by token. Every one of these events updates the messages() Signal, and your template re-renders the new content automatically.
Performance: Signals vs Alternatives
High-frequency token streaming puts unique pressure on a frontend framework. Here's why Signals with OnPush outperform the alternatives.
| Approach | Token update cost | Memory overhead | Cleanup required |
|---|---|---|---|
| Signals + OnPush | Marks only reading components | None beyond Signal | Automatic |
| Observable + async pipe | Creates/destroys subscriptions per @if block | Subscription objects | Pipe handles it |
| Observable + manual subscribe | Full component check if you forget markForCheck() | Subscription tracking | Manual unsubscribe |
| Default change detection | Checks entire component tree | None | None |
For a typical chat UI receiving 30-50 tokens per second:
- Signals + OnPush: Only the message list component and its direct ancestors are checked. The sidebar, header, settings panel — all skipped.
- Default strategy: Every component in the tree is checked 30-50 times per second, even components with no streaming data.
- Observable + async pipe: Works correctly but creates and destroys subscriptions each time an
@ifor@forblock re-evaluates, adding GC pressure during rapid streaming.
Signals use referential equality (===) by default. agent() creates new array references for messages() only when the array actually changes (a new token arrives). Between updates, reading messages() returns the same reference and skips downstream recomputation. For custom equality, pass an equal function when creating a computed().
What's Next
How LangGraph agent state flows into Angular Signals and how to structure complex state.
Configure stream modes, handle token-by-token rendering, and manage concurrent streams.
Understand the Python agent patterns that produce the events Signals consume.
Full reference for every Signal, method, and option on LangGraphAgent.
Build human-in-the-loop approval flows that pause and resume the agent.
Deep dive into change detection optimization for streaming applications.