Angular’s introduction of Signals has generated both excitement and confusion. For many developers, Signals appear to be “simpler observables” or a more convenient way to trigger updates without subscriptions. Others attempt to map them directly onto familiar RxJS patterns, expecting emissions, operators, and event-style coordination.
Both interpretations miss the point.
Signals are not primarily an event system, and they are not designed to replace RxJS. They represent a different way of modeling application behavior, one that centers on current state and explicit dependencies rather than sequences of events. This distinction is subtle at first, but it has significant consequences for how applications are structured and reasoned about over time.
In a previous article, “Angular Signal Forms: From event pipelines to signal-driven state,” we reframed form behavior as a state-driven problem rather than an event-driven one. That shift raises an important follow-up question: what kind of reactive primitive is best suited for expressing state and derived behavior? To answer that, we need to understand Signals on their own terms, independent of any specific feature such as forms.
This article examines Angular Signals as a reactivity model rather than a convenience API. By clarifying what Signals are and, just as importantly, what they are not, we can better understand where they fit alongside RxJS and why they align so naturally with state-heavy problems such as form modeling.
Signals as a state primitive (not an event system)
To understand why Signals are a good fit for form modeling, it helps to be precise about what Signals are, and just as importantly, what they are not.
Signals are not an event system. They do not represent a sequence of things that happened over time. Instead, a signal represents a current value, along with a dependency graph that describes how other values derive from it. When a signal changes, Angular does not broadcast an event. It simply marks dependent computations as stale and reevaluates them the next time they are read. This is what we mean by fine-grained change detection control.
This distinction may seem subtle, but it has profound implications for how we reason about application logic.
Reactive streams encourage developers to think in terms of emissions. When something changes, subscribers are notified, operators transform the stream, and side effects occur in response. This model is extremely powerful for asynchronous workflows, but it introduces temporal reasoning even when time is not an essential concern. Developers must ask not only what the current state is, but how it arrived there and which emission triggered a particular piece of logic.
Signals, by contrast, encourage a declarative, pull-based model. A computed signal does not react to changes as they occur. Instead, it declares that its value depends on other signals. When those dependencies change, the computed value is simply recomputed the next time it is accessed. There is no notion of subscription order, missed emissions, or stale listeners.
This pull-based model aligns naturally with form state. At any moment, a form has a well-defined set of values. From those values, validity, error messages, and UI flags can be derived. These relationships do not depend on the sequence of changes that led to the current state. They depend only on the current state itself.
This is why Signals feel simpler when applied to state-heavy problems. They shift the developer’s focus away from orchestration and toward declaration. Instead of asking “What should happen when this changes?”, the question becomes “What does this value depend on?”
It is important to note that this does not make Signals a replacement for RxJS. Angular still relies on observables for asynchronous streams, external events, and integration with APIs that produce values over time. Signals and RxJS serve different purposes. In the context of forms, Signals are best used to represent state and derived state, while RxJS remains useful for asynchronous side effects and integration points.
By keeping this distinction clear, we avoid the trap of using Signals as a less expressive event system. Instead, we use them for what they do best: modeling state in a way that is explicit, deterministic, and easy to reason about.
Designing a signal-first form model with Angular Signal Forms
Before looking at any concrete implementation, it is worth clarifying what a “signal-first” form model actually implies. The goal is not to introduce a new abstraction that replaces Angular Forms, nor is it to hide form behavior behind another layer of indirection. Instead, the intent is to reorient how form state is represented and reasoned about.
In a signal-first approach, the form’s data model is treated as the single source of truth. Signals are used to represent that state directly, rather than mirroring it through control hierarchies or intermediary objects. The form itself becomes a projection over the state, attaching semantics such as validation, interaction metadata, and submission behavior without duplicating or owning the data.
This distinction is subtle but important. Traditional form models often encourage developers to think of the form as the container of state, with values flowing in and out through events. A signal-first model reverses that relationship. State exists independently of the form, and the form derives its behavior from that state. This makes it easier to inspect, reason about, and test form behavior, because the underlying data remains explicit and accessible.
The examples in this section are therefore intentionally minimal. They are not meant to demonstrate every feature of Angular Signal Forms, but to illustrate how a state-driven representation reshapes form architecture. The emphasis is on structure and intent rather than mechanics. More detailed implementation concerns, such as asynchronous validation, persistence, and UI composition, are explored in the following article.
Once we accept that form behavior is largely derived from state, the next question becomes how that idea is expressed in Angular itself. Angular’s Signal Forms API is a direct response to this shift in thinking. Rather than modeling forms as trees of controls emitting events, Signal Forms begin with a signal-backed model and layer form behavior validation, interaction state, and submission on top of it.
The starting point is still the same: a plain data model representing the values the form collects. In a signal-first approach, this model is wrapped in a writable signal and treated as the single source of truth. There is no duplication of state between the UI and the form model, and no need to synchronize multiple representations of the same data.
From this model signal, a form instance is created using Angular’s form() function. The role of this function is not to introduce a second state container, but to attach form semantics to an existing state object. The form instance provides structured access to fields, validation results, and interaction metadata, all of which are exposed as signals.
Validation is declared through a schema function passed to form(). This schema associates validation rules directly with specific fields in the model. Built-in validators such as required() and email() express constraints declaratively, and Angular automatically reevaluates them whenever the underlying values change. Validation results are not stored imperatively; they are derived and exposed through field-level signals such as invalid(), errors(), and pending().
This design is significant because it keeps validation aligned with the mental model established earlier. Validation rules do not “run” in response to events. They describe constraints on state. When state changes, derived validation state updates automatically, without subscriptions, listeners, or life-cycle hooks.
Angular Signals Form example
A minimal example illustrates the shape of this approach. The model remains a simple interface, and the signal holds the current form values.
interface RegistrationData {
email: string;
password: string;
confirmPassword: string;
acceptedTerms: boolean;
}
The form is then created by passing this model signal into form(), along with a schema that declares validation rules.
const registrationModel = signal({
email: '',
password: '',
confirmPassword: '',
acceptedTerms: false,
});
const registrationForm = form(registrationModel, (schema) => {
required(schema.email, { message: 'Email is required' });
email(schema.email, { message: 'Enter a valid email address' });
required(schema.password, { message: 'Password is required' });
required(schema.confirmPassword, { message: 'Please confirm your password' });
required(schema.acceptedTerms, {
message: 'You must accept the terms to continue',
});
});
What matters here is not the syntax, but the structure. The model signal defines what the form is. The schema defines what constraints apply. Angular takes responsibility for deriving field state and exposing it through signals that the UI can consume directly.
Each field now has a clear, inspectable state. Whether a field is valid, invalid, touched, or pending is no longer inferred by tracing event streams or subscription chains. It is available as a signal, derived from the current model and the declared rules. This makes form behavior easier to reason about, test, and debug.
Just as importantly, this model scales naturally. Cross-field validation, such as checking that two password fields match, can be expressed declaratively using schema-level logic that reads from multiple fields. Form-level state, such as whether submission should be allowed, is derived rather than toggled imperatively. The form remains a projection of the state, not a controller of behavior.
I have avoided discussing templates or DOM integration here. The purpose of this section is to show that Angular’s Signal Forms align closely with the first-principles model introduced above. They do not replace that model; they formalize it.
In the next article in this series, we will connect this signal-first form to an actual Angular component. We will bind fields to inputs, render validation feedback using field state signals, and implement submission logic. This implementation will form the foundation of the GitHub example that accompanies this series and will be extended in later articles to cover asynchronous validation, persistence, and hybrid approaches.
The power of Signals
Angular Signals represent a deliberate shift in how reactivity is expressed within the framework. Rather than focusing on events, emissions, and coordination, Signals encourage developers to describe relationships between values. Computation becomes declarative, dependencies become explicit, and behavior becomes easier to reason about by inspection rather than reconstruction.
This does not diminish the role of RxJS. Event streams, asynchronous workflows, and integration with external systems remain essential parts of modern applications. Signals and RxJS solve different problems, and treating them as interchangeable inevitably leads to confusion. When each is used for what it does best — Signals for state and derivation, RxJS for coordination and side effects — the resulting architecture becomes clearer and more maintainable.
Viewed through this lens, the appeal of Signals is not novelty, but alignment. Signals map closely to how developers already think about state: as something that exists now, from which other values can be derived deterministically. This alignment reduces cognitive overhead, particularly in parts of an application where behavior is dominated by state rather than time.
With this understanding in place, we can now turn to practice. The next article applies these ideas to a concrete Angular example, showing how a signal-first approach reshapes form modeling, validation, and UI logic without reintroducing event-driven complexity.
Go to Source
Author: