❌

Reading view

Angular Signals in practice: Building a signal-first form in Angular

Understanding a reactivity model in the abstract is useful, but it is ultimately incomplete without seeing how it shapes real application code. Concepts such as state, derivation, and explicit dependencies only become meaningful when they influence how forms are built, validated, and maintained in practice.

In two previous articles, “Angular Signal Forms: From event pipelines to signal-driven state” and “Angular Signals explained: How pull-based reactivity changes how we model state,” we reframed form behavior as a state-driven problem and examined Angular Signals as a pull-based reactivity model well-suited to that kind of work. The natural next step is to apply those ideas to an actual Angular form and observe how the architecture changes when state becomes the primary concern.

This article focuses on a concrete example: a modest but realistic registration form. Rather than introducing new concepts, the goal here is to make earlier ideas tangible. We will see how a signal-backed model reshapes validation, interaction state, and submission logic, and how much coordination logic simply disappears when form behavior is expressed declaratively.

The focus here is not on novelty or completeness, but on making the underlying ideas easier to reason about. By walking through a signal-first form from model definition to submission, we can evaluate whether this approach truly reduces complexity and where it introduces new trade-offs that teams should understand before adopting it more broadly.

Read the series:

Implementing a signal-first registration form

With the conceptual groundwork in place, we can now turn theory into a concrete implementation. In this section, we will build a fully working registration form using Angular’s Signal Forms API. This example is deliberately modest in scope, but it is designed to serve as the foundation for the rest of the series. Each subsequent article will extend this same example rather than introducing a new one.

The form collects an email address, a password, a confirmation password, and explicit acceptance of terms. While simple on the surface, this structure allows us to explore field-level validation, cross-field constraints, interaction state, and submission behavior, all without reverting to event-driven form logic.

Project setup and structure

The example assumes a standard Angular application created with the Angular CLI and configured to use Signals (Angular 17+). The Signal Forms APIs (Angular 21+) live under @angular/forms/signals, which must be explicitly imported.

https://github.com/sonukapoor/angular-signal-forms

The folder structure is intentionally conservative:

src/
  app/
    registration/
      registration.component.ts
      registration.component.html
      registration.model.ts

Separating the model from the component keeps form state independent of presentation. This becomes increasingly valuable as the form grows or is reused across multiple components.

Defining the form model

We begin by defining the shape of the data that the form collects. This is a plain TypeScript interface with no Angular dependencies. Treating the form model as a simple data structure reinforces the idea that the form’s values are just state.

// registration.model.ts
export interface RegistrationData {
  email: string;
  password: string;
  confirmPassword: string;
  acceptedTerms: boolean;
}

This interface mirrors what would typically be sent to a back-end API. There is no duplication of state, no separate “form value” object, and no mapping required at submission time.

Creating the signal-backed form

The form itself is created in the component using a writable signal as the source of truth. The form() function attaches form semantics validation, field state, and submission to that signal.

// registration.component.ts
import { CommonModule } from "@angular/common";
import { Component, signal } from "@angular/core";
import {
  email,
  form,
  FormField,
  required,
  submit,
} from "@angular/forms/signals";
import { RegistrationData } from "./registration.model";

@Component({
  selector: "app-registration",
  imports: [FormField, CommonModule],
  templateUrl: "./registration.html",
  styleUrl: "./registration.css",
})
export class Registration {
  readonly model = signal({
    email: "",
    password: "",
    confirmPassword: "",
    acceptedTerms: false,
  });

  readonly registrationForm = form(this.model, (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",
    });
  });

  async onSubmit(event?: Event) {
    event?.preventDefault();

    await submit(this.registrationForm, (value) => {
      console.log(value());
      // Mock Server Call
      return Promise.resolve([
        {
          kind: "EmailAlreadyExists",
          field: this.registrationForm.email,
          error: { kind: "server", message: "Email already taken" },
        },
      ]);
    });
  }
}

Several design decisions are worth noting.

First, the model signal is defined as read-only. All mutations to the model occur through form bindings, not ad hoc assignments in the component. This keeps the component declarative and avoids the temptation to manipulate form state imperatively.

Second, validation is declared in one place. The schema function describes constraints on the model without introducing control trees, validator arrays, or observable pipelines. Angular takes responsibility for re-running validation whenever the model changes.

Finally, submission logic is explicit. The submit() helper ensures that the form is valid before invoking the callback, and it passes the current model value directly. There is no need to check flags or manually extract values.

Binding the form to the template

With the form defined, the next step is to bind it to the template. Signal Forms provide the [formField] directive, which connects an input element directly to a field in the form schema.





  
@if (registrationForm().errors().length > 0) {
@for (error of registrationForm().errors(); track error.message) {

{{ error.kind }}

}
}
@if ( registrationForm.acceptedTerms().invalid() && registrationForm.acceptedTerms().touched() ) {

{{ registrationForm.acceptedTerms().errors()[0].message }}

}
@if ( registrationForm.confirmPassword().invalid() && registrationForm.confirmPassword().touched() ) {

{{ registrationForm.confirmPassword().errors()[0].message }}

}
@if ( registrationForm.password().invalid() && registrationForm.password().touched() ) {

{{ registrationForm.password().errors()[0].message }}

}
@if ( registrationForm.email().invalid() && registrationForm.email().touched() ) {

{{ registrationForm.email().errors()[0].message }}

}

What stands out here is the absence of indirection. Each input binds directly to a field. Validation state is accessed through signals such as invalid() and touched(). Error messages are read from a structured error object, not reconstructed manually.

This template contains no subscriptions, no async pipes, and no event handlers for value changes. The UI simply reflects the current form state.

Interaction state and user experience

One of the common criticisms of declarative form models is that they obscure user interaction logic. Signal Forms address this directly by exposing interaction metadata as signals.

The touched() signal determines whether a field has been interacted with. By combining it with invalid(), we control when validation messages appear. This logic remains purely declarative: the template describes when errors should be visible, and Angular ensures the signals stay up-to-date.

The disabled state of the submit button is derived from registrationForm.invalid(). There is no need to manually enable or disable it in response to events. If the form becomes valid, the button is enabled automatically.

Why this scales

Even at this early stage, several advantages of a signal-first form model are apparent. The form’s behavior is expressed in terms of state and derivation, not events. The model, validation rules, and UI bindings are clearly separated. There is no duplication of logic between the component and the template.

As the form grows, this structure holds. Additional fields introduce additional schema entries and template bindings, not new subscription logic. Cross-field validation can be added declaratively. Asynchronous validation and persistence can be layered on without rewriting the core model.

Most importantly, the form remains inspectable. At any point during execution, the model signal reflects the current state of the form. Derived state validity, errors, and UI flags can be understood by reading the code, not by tracing runtime behavior.

What we did not solve yet (and why)

At this stage, it would be easy to walk away with the impression that Signal Forms eliminates most of the hard problems associated with form handling. That impression would be misleading. What we have built so far is intentionally incomplete, not because the approach falls short, but because introducing too much too early obscures the value of the underlying model.

One area we have deliberately postponed is cross-field validation that expresses richer business rules. Many real-world forms depend on relationships between fields rather than isolated constraints. Password confirmation is a familiar example, but more complex scenarios quickly arise in enterprise applications. While Signal Forms support these patterns, introducing them before establishing a clear understanding of derived state risks turns validation back into an imperative exercise rather than a declarative one.

We have also avoided asynchronous validation. Server-backed checks introduce latency, partial failure, cancellation, and race conditions. These are not trivial concerns, and treating them casually often leads to subtle bugs and confusing user experiences. Although Signal Forms provide the necessary hooks to model asynchronous behavior, doing so responsibly requires a careful discussion of pending state, effects, and life-cycle boundaries. That discussion belongs in its own article.

Another omission is persistence and synchronization. Many forms need to autosave drafts, synchronize state with local storage, or react to changes by triggering external side effects. These behaviors are not part of the form state itself; they are consequences of state changes. Treating them as such is essential to keeping the architecture comprehensible. Introducing persistence too early would blur the distinction between state and reaction that this article has worked to establish.

Finally, this article has not addressed migration and interoperability. Few teams are starting from a blank slate. Most will adopt Signal Forms incrementally within applications that already rely on reactive forms or template-driven forms. Hybrid approaches, bridging strategies, and gradual refactors are all critical topics, but they presuppose familiarity with both paradigms. Addressing migration before establishing a solid signal-first mental model would undermine that foundation.

These omissions are intentional. A form architecture that tries to do everything at once often ends up doing nothing clearly. By focusing on the core ideas of state, derivation, and declarative validation, we create a base that can absorb additional complexity without collapsing under it.

Signal Forms in the context of Angular’s evolution

To fully appreciate Signal Forms, it helps to step back and view them not as an isolated feature, but as part of a broader shift in Angular’s design philosophy.

For much of its history, Angular emphasized declarative templates paired with imperative coordination in component classes. RxJS became the backbone of that coordination, providing a powerful abstraction for handling asynchronous workflows, user input, and external events. This model scaled well, but it also encouraged developers to express state indirectly through streams and subscriptions.

Signals represent a deliberate recalibration. They re-center Angular’s reactivity model around state and derivation, rather than events and emissions. This shift is visible across the framework: in component inputs, change detection, and now forms. Signal Forms are not an attempt to replace everything that came before; they are an attempt to make the most common use case, modeling and deriving state, simpler and more explicit.

Framed this way, the design of Signal Forms aligns more closely with state-driven form behavior. The requirement to start with a model signal reflects the idea that the state should have a single, inspectable source of truth. Schema-based validation aligns with the notion that constraints are properties of state, not behaviors triggered by events. Field state exposed as signals reinforces the idea that validity, errors, and interaction metadata are derived values that should be read, not managed.

It is also worth noting that Signal Forms do not attempt to abstract away form behavior. They do not hide form state behind opaque classes or life-cycle hooks. They do not require developers to think in terms of control hierarchies or subscription graphs. Instead, they expose form behavior directly, making it easier to reason about how values, validation, and UI feedback relate to one another.

This approach aligns closely with other recent changes in Angular, including the introduction of modern template control flow and a stronger emphasis on explicit data dependencies. Together, these features point toward a framework that favors clarity over indirection and composition over orchestration.

Importantly, Signal Forms are still evolving. Their APIs may change, and their surface area will almost certainly expand. That is precisely why grounding them in first principles matters. Developers who understand why Signal Forms work the way they do will be far better equipped to adapt as the APIs mature.

This article has intentionally avoided duplicating documentation or enumerating every available feature. Instead, it has focused on establishing a conceptual framework that makes the official APIs feel intuitive rather than surprising. When viewed this way, Signal Forms are not a new way to write forms; they are a clearer expression of what forms have always been.

A new way to think about forms

Building the registration form in this article reveals a quiet but important shift. The reduction in complexity does not come from fewer features or simpler requirements. It comes from expressing form behavior in terms of state and derivation rather than orchestration and reaction.

By treating the data model as the single source of truth, validation rules as declarative constraints, and UI behavior as derived from current conditions, much of the coordination logic that typically surrounds forms becomes unnecessary. There are fewer subscriptions to manage, fewer flags to synchronize, and fewer life-cycle concerns to reason about. Form behavior becomes easier to inspect because it is visible directly in the relationships between values.

This approach does not eliminate the hard problems associated with forms. Asynchronous validation, persistence, and interoperability with existing Angular Forms APIs still require careful design. What changes is where that complexity lives. Instead of being interwoven with state representation, those concerns are layered explicitly on top of a clear foundation.

Signal-first forms are not a universal replacement for existing patterns, nor are they a shortcut to simpler applications. They are, however, a strong example of how aligning APIs with first principles can reduce cognitive overhead and improve maintainability over time. For teams building large, state-heavy forms, this alignment can make the difference between code that merely works and code that continues to evolve without friction.

  •  

Databricks targets AI operations bottlenecks with ZeroOps

Databricks is pitching a fix for what it sees as the growing operations mess in enterprise AI. With the launch of Genie ZeroOps, unveiled at its Data + AI Summit, the company is targeting a problem many data teams know too well: it’s no longer building pipelines and models that hurts, it’s keeping them running.

As data estates sprawl and AI workloads multiply, engineering time is increasingly eaten up by maintenance. Meanwhile, AI coding tools are accelerating development, churning out even more assets that need oversight, widening the gap between how fast teams can build and how much they have to manage.

Databricks Genie ZeroOps is a new agentic operations capability that is designed to automate the monitoring, investigation, and remediation of issues across data and AI workloads.

Currently in private preview, ZeroOps uses an AI agent to identify anomalies, trace root causes using metadata and lineage information via Unity Catalog, generate proposed fixes, and then test those fixes in an isolated environment before pushing them out for human review to be applied in production.

Targeting real operational complexity?

Genie ZeroOps addresses a legitimate enterprise challenge around operational complexity, particularly the growing burden of maintaining data and AI workloads in production, analysts say.

“Most data teams spend more time keeping pipelines and models alive than building new ones,” said Amit Chandak, chief analytics officer at IT consulting firm Kanerika.

Echoing Chandak, independent consultant David Linthicum said enterprises continue to grapple with deployment drift, incident response, compliance checks, and root-cause analysis across increasingly fragmented data and AI estates.

Those challenges, echoed Victor Coimbra, CTO of IT consulting firm Artefact, are compounded by the emergence of agentic coding tools that accelerate the development of assets, such as machine learning pipelines and models that need “babysitting.”

That maintenance burden carries a significant productivity cost, said Robert Kramer, managing partner at KramerERP, noting that activities such as managing infrastructure, deployment environments, support processes, and operational workflows consume time without directly creating business value.

Those productivity drains, according to Coimbra, have proven difficult to eliminate despite the emergence and widespread adoption of automated observability and governance tools.

“What is different here is the agentic piece. Databricks is trying to move from tools that alert humans to systems that diagnose issues, propose fixes, and validate them in a governed environment without breaking anything in production,” echoed Stephanie Walter, practice leader of AI stack at HyperFRAME Research.

Shifting the role of platform teams

That shift, according to analysts, could change the way most enterprise platforms and development teams work currently.

“Skilled engineers spend the majority of their time on toil. If the ZeroOps agent, in the background, handles monitoring, investigation, and fix-proposal, engineers shift from doing the operational work to reviewing it. The traditional split between ‘people who build’ and ‘people who keep things running’ starts to blur,” said Ashish Chaturvedi, leader of executive research at HFS Research.

“Additionally, this would also mean that platform teams (engineers responsible for maintenance) can focus on genuinely novel failures rather than the repetitive ones,” Chaturvedi added.

The shift, according to Coimbra, could also affect how enterprises scale platform teams: “They can stop hiring operations staff in lockstep with every new pipeline. The same team can cover a lot more.”

Given that the capability is still in preview, Kanerika’s Chandak pointed out that the headcount reduction claims may be overstated.

ZeroOps could instead pose the risk of “skill atrophy,” Chandak said.

“If engineers stop debugging because the agent does it, the team’s ability to handle the cases the agent cannot handle becomes a real exposure,” Coimbra added.

What ZeroOps could mean for CIOs

Genie ZeroOps could be attractive to CIOs because it links innovation capacity with operational discipline rather than forcing a tradeoff between the two, Linthicum said.

“The appeal is straightforward: reduce operational drag, shorten deployment cycles, improve service resilience, and enforce governance without scaling headcount at the same rate as workloads,” Linthicum said.

That combination of efficiency and reliability could help CIOs rein in one of the biggest costs associated with operating data and AI environments, Chaturvedi said. “ZeroOps attacks time spent on maintenance. CIOs have watched their data engineering budgets balloon while the proportion of that spend going to net-new value shrinks.”

Linthicum warned that CIOs should consider the new offering with calculated skepticism and seek metrics to validate Databricks’ claims.

“The headline metrics are mean time to detect and mean time to resolve, plus the share of incidents the agent closes without a human stepping in. Those tell you whether it is actually removing the operational complexities that it promises,” Kanerika’s Chandak echoed.

“Underneath these metrics, CIOs should track the accuracy of their root cause calls, the false positive rate on proposed fixes, and the proportion of fixes engineers approve without editing, because that last number is the real trust signal. On cost, they should measure cost per incident handled against the human baseline, net of agent compute,” Chandak added.

That scrutiny, Chandak further added, is even more important for CIOs because Databricks is entering an emerging category.

“Most vendor agent announcements target the build and use layers, helping people write code or ask questions of their data. ZeroOps targets the operate layer, which is less crowded,” Chandak said.

ENDS

  •  

France’s OVHcloud bets on frontier AI as Europe seeks alternatives to US models

France’s OVHcloud is moving beyond cloud infrastructure into frontier AI model development, a shift that could test whether Europe can produce another serious alternative to US and Chinese AI systems.

The company, one of Europe’s leading homegrown cloud providers, plans to train a family of models from scratch and aims to open-source them once they meet its performance targets, CEO Octave Klaba told Reuters.

The move would put OVHcloud in closer comparison with Mistral AI, the Paris-based model developer that has become Europe’s most visible challenger to US AI labs.

Klaba said the economics of building advanced AI models have changed, with improvements in chips, training methods, and synthetic data reducing the cost of a project that may once have required about $1.15 billion (€1 billion) to now cost less than $230 million (€200 million).

Reuters reported that OVHcloud said one of its models has completed pre-training on Jupiter, the Germany-based EuroHPC supercomputer described as Europe’s fastest and its first exascale system, though the company has not yet disclosed detailed performance benchmarks.

This comes as European governments and enterprises are increasingly having to assess AI infrastructure through the lens of data governance and continuity of access, rather than performance alone.

Those concerns were sharpened this month after Anthropic said a US government export-control directive required it to suspend access to its Fable 5 and Mythos 5 models by foreign nationals inside and outside the US.

Training is only the opening cost

OVHcloud’s lower cost estimate does not capture the full cost of becoming a frontier AI model provider, said Neil Shah, vice president for research and partner at Counterpoint Research.

The $230 million (€200 million) figure likely refers mainly to the initial training run, Shah said. Once trained, however, models require continued investment because they can become depreciating assets if they are not improved with fresh data.

OVHcloud would also need to spend on fine-tuning, post-training, sovereign infrastructure, storage, security, distribution, and enterprise support. It would also need enough scale to make model serving economically viable against established AI providers such as Google and Anthropic.

“Model is seen as a depreciating asset if it is not consistently trained and kept fresh with the data,” Shah said.

That makes OVHcloud’s plan a test not only of technical capability, but also of policy support and economic viability. If the company falls short, enterprises may be reluctant to shift workloads away from more established models.

The lower training cost could still give OVHcloud a credible starting point, said Charlie Dai, principal analyst at Forrester.

The budget range can be enough to produce a credible frontier model as efficiency gains reduce the cost of entry, Dai said. But enterprise competitiveness will depend on sustained capabilities beyond training, including inference efficiency, data pipelines, evaluation frameworks, and ecosystem reach.

Buyers need proof

OVHcloud’s plan remains an expression of intent rather than demonstrated capability, said Sanchit Vir Gogia, chief analyst at Greyhound Research, pointing to the absence of published benchmarks and other details.

“$200 million now buys a serious training run,” Gogia said. “It does not buy a serious enterprise AI franchise.”

Gogia said questions around sovereignty also extend to the infrastructure used to train the model, noting that pre-training was run on Jupiter rather than on infrastructure owned or controlled by OVHcloud.

The system is a publicly owned European supercomputer in Germany that runs on American silicon, Gogia said, adding that this shows how partial European AI sovereignty remains.

CIOs will need evidence that the models can be supported in production, governed effectively, audited when needed, and exited without major disruption.

Gogia said a European-owned model could reduce some dependence on US and Chinese providers, but would not remove jurisdictional risk. “Sovereignty does not abolish the off switch,” he said. “It changes whose hand rests upon it.”

OVHcloud’s move into model development could also alter the lock-in risks enterprises need to assess, Gogia said. Customers may be able to move cloud infrastructure later, but find it harder to shift AI workloads once applications and processes are built around a provider’s models and governance tools.

The article originally appeared on ComputerWorld.

  •  

Why AI coding debt is different

In hardware, when you ship something broken, the consequences are severe and often irreversible. That’s the world I worked in for years, in verification roles at Mellanox and later at Alibaba. The stakes forced the industry to build a rigorous verification culture. You proved designs worked before they left the building.

In software, verification disciplines look like CI/CD pipelines, static analysis, canary deployments, and observability. But those systems were built around code written at human speed, with human comprehension baked into the process. AI code generation has broken that assumption. The writing process can no longer be trusted to carry institutional knowledge and judgment into the codebase. The industry is being pushed toward the kind of rigorous verification culture that hardware engineers have practiced for decades.

Enterprises are generating code faster than at any point in history. Google recently disclosed that 75% of the company’s new code is now AI-generated. Meta has set internal targets requiring most of its engineers to generate the majority of their committed code with AI tools by mid-2026. The velocity gains are significant. But a growing body of evidence suggests the industry is accumulating a new form of technical debt, one that is less visible than the traditional kind and harder to unwind. It’s also preventable, and the organizations that get ahead of it will have a meaningful advantage over those that don’t.

Understand what makes this debt different

The standard narrative frames this as AI writing bad code. That’s not quite right. The more precise problem is cognitive debt: the loss of understanding of how and why software was built the way it was.

When a human writes code, something else happens alongside the typing. They simulate edge cases, reason through dependencies, and make judgment calls grounded in organizational context, including the business requirements behind a feature, the best practices the team has established, and the reasoning behind past architectural choices. That cognitive loop is how institutional knowledge gets built. When AI writes the code, you can get output that is syntactically correct, passes CI, ships cleanly, and leaves no one holding the mental model. The code works until something changes or breaks, and then the team is excavating a black box.

This is distinct from traditional technical debt, which is messy code. Cognitive debt is invisible code that functions but that nobody truly owns. And it compounds faster, because the same velocity that makes AI generation attractive is what prevents anyone from stopping to build the understanding that maintainability requires.

GitClear’s analysis of 211 million changed lines of code across major repositories found that during 2024, duplicate code blocks of five or more lines increased eightfold, while refactoring dropped from 25% to under 10% of all code changes. Refactoring is the slow, unglamorous work that keeps codebases healthy, and developers are doing far less of it. Google’s 2024 DORA report found that a 25% increase in AI adoption correlates with a 7.2% decrease in delivery stability. DORA analysts note that the root cause isn’t flawed code per se; AI inflates batch sizes, and larger changesets have always been riskier to ship.

These findings aren’t indictments of AI-assisted development. They’re diagnostics, and they point toward a specific set of fixes.

Close the context gap first

In a survey of 609 developers we conducted last year, 65% said AI misses relevant context during critical tasks like refactoring, writing tests, or reviewing code. Context is the primary driver of AI code quality, and it’s where most enterprise organizations are underinvesting.

When an AI tool generates code without access to your organization’s architectural decisions, historical pull requests, security policies, or existing module patterns, you get solutions that are locally correct but globally incoherent. Closing that gap requires context engineering: ensuring the tools and agents you use have access, at the right moment, to the right organizational knowledge, and the judgment to determine what is actually relevant for a given task. A retrieval system that surfaces too much irrelevant context can degrade output quality as readily as one that surfaces too little. The specific tooling matters less than the discipline. Context infrastructure needs to be actively maintained, not indexed once and forgotten.

Build this infrastructure before you scale AI generation. Retrofitting is significantly harder. Treat it the way you treat your CI pipeline, as a prerequisite for safe production deployment.

Consider what happens when a team has built this context infrastructure well. Their code review tooling knows about a deprecated internal API, because that deprecation decision lives in months of past pull request discussions that have been indexed and surfaced. When generated code references the old API, the review flags it. Without that context layer, the same mistake gets waved through every time. That’s the kind of institutional knowledge that evaporates when humans stop writing every line of code, and that you have to actively work to preserve.

Build a verification layer that matches your generation velocity

Almost all of the investment in AI-assisted development has gone into generation. Very little has gone into verification. That imbalance is where the tech debt accumulates.

I think of these as the blue team and the red team. The blue team covers code generation, autocomplete, and agentic coding. It’s getting the headlines, the budgets, and the product launches. The red team covers integrity checks, behavior coverage, and alignment with organizational standards. In most organizations, it’s an afterthought. A CI pipeline catches obvious failures. A code review might happen, but reviewers are overwhelmed by the volume of AI-generated output and cannot meaningfully evaluate all of it. The result is code with a veneer of having been reviewed without anyone having actually understood it.

The Crowdstrike outage of 2024 is worth keeping in mind here. AI didn’t generate the problematic code, but the incident illustrated what happens when a single software error propagates through production systems without sufficient verification. That exposure multiplies when code is being generated faster than humans can understand it.

A real verification layer means automated analysis that evaluates whether generated code aligns with your organization’s best practices, architectural standards, and compliance requirements. It means test coverage that reflects intended behavior, not only the happy path the AI chose to generate tests for. And it means traceability: a connection between the requirement and the implementation, so that six months from now, someone can understand what the code does and why it exists.

The numbers support investment here. In the same developer survey, teams that integrated AI into their code review workflow saw quality improvements in 81% of cases, compared to 55% for comparable teams without it. 

Make ownership non-negotiable

Every piece of AI-generated code in production needs an accountable human who understands it well enough to maintain it. This is harder than it sounds, and it’s where most organizations are falling short.

The same velocity that makes AI generation attractive also creates pressure to skip the slow work of genuine comprehension. A developer reviews a 500-line pull request that an AI generated in three minutes and faces a real choice: spend two hours actually understanding it, or approve it because it looks right, passes the test, and “LGTM” (looks good to me). 

Real ownership means slowing down generation velocity enough to allow for meaningful review, and being explicit with your team that this is the right trade-off. When that doesn’t happen, you’ve started building your next legacy system.

What to do this quarter

The good news is that none of this requires a multi-year transformation. The structural problems are real, but they have concrete solutions, and engineering leaders can make meaningful progress on all three fronts without waiting for the next budget cycle.

  • Audit your context infrastructure. If your AI tools are generating code without access to your organization’s architectural decisions, deprecated APIs, and security policies, fix that before expanding generation velocity further. The quality of context determines the quality of output.
  • Invest in the red team. Establish an automated code quality and governance layer that evaluates generated code against your specific organizational standards, beyond functional correctness. This is a distinct investment from your code generation tooling and needs to be treated as such.
  • Establish ownership explicitly. Every AI-generated feature in production should have a human owner who genuinely understands it. Make that a formal requirement.

The organizations that get this right will find that AI generation becomes far more reliable once it has a verification layer underneath it. The ones that don’t will keep shipping faster while understanding their systems less, until the accumulated debt forces a reckoning. That’s a solvable problem. The question is whether you solve it now or later.

—

New Tech Forum provides a venue for technology leaders—including vendors and other outside contributors—to explore and discuss emerging enterprise technology in unprecedented depth and breadth. The selection is subjective, based on our pick of the technologies we believe to be important and of greatest interest to InfoWorld readers. InfoWorld does not accept marketing collateral for publication and reserves the right to edit all contributed content. Send all inquiries to doug_dineley@foundryco.com.

  •  

Building a signal-first form in Angular

Understanding a reactivity model in the abstract is useful, but it is ultimately incomplete without seeing how it shapes real application code. Concepts such as state, derivation, and explicit dependencies only become meaningful when they influence how forms are built, validated, and maintained in practice.

In two previous articles, “Angular Signal Forms: From event pipelines to signal-driven state” and “Angular Signals explained: How pull-based reactivity changes how we model state,” we reframed form behavior as a state-driven problem and examined Angular Signals as a pull-based reactivity model well-suited to that kind of work. The natural next step is to apply those ideas to an actual Angular form and observe how the architecture changes when state becomes the primary concern.

This article focuses on a concrete example: a modest but realistic registration form. Rather than introducing new concepts, the goal here is to make earlier ideas tangible. We will see how a signal-backed model reshapes validation, interaction state, and submission logic, and how much coordination logic simply disappears when form behavior is expressed declaratively.

The focus here is not on novelty or completeness, but on making the underlying ideas easier to reason about. By walking through a signal-first form from model definition to submission, we can evaluate whether this approach truly reduces complexity and where it introduces new trade-offs that teams should understand before adopting it more broadly.

Implementing a signal-first registration form

With the conceptual groundwork in place, we can now turn theory into a concrete implementation. In this section, we will build a fully working registration form using Angular’s Signal Forms API. This example is deliberately modest in scope, but it is designed to serve as the foundation for the rest of the series. Each subsequent article will extend this same example rather than introducing a new one.

The form collects an email address, a password, a confirmation password, and explicit acceptance of terms. While simple on the surface, this structure allows us to explore field-level validation, cross-field constraints, interaction state, and submission behavior, all without reverting to event-driven form logic.

Project setup and structure

The example assumes a standard Angular application created with the Angular CLI and configured to use Signals (Angular 17+). The Signal Forms APIs (Angular 21+) live under @angular/forms/signals, which must be explicitly imported.

https://github.com/sonukapoor/angular-signal-forms

The folder structure is intentionally conservative:

src/
  app/
    registration/
      registration.component.ts
      registration.component.html
      registration.model.ts

Separating the model from the component keeps form state independent of presentation. This becomes increasingly valuable as the form grows or is reused across multiple components.

Defining the form model

We begin by defining the shape of the data that the form collects. This is a plain TypeScript interface with no Angular dependencies. Treating the form model as a simple data structure reinforces the idea that the form’s values are just state.

// registration.model.ts
export interface RegistrationData {
  email: string;
  password: string;
  confirmPassword: string;
  acceptedTerms: boolean;
}

This interface mirrors what would typically be sent to a back-end API. There is no duplication of state, no separate “form value” object, and no mapping required at submission time.

Creating the signal-backed form

The form itself is created in the component using a writable signal as the source of truth. The form() function attaches form semantics validation, field state, and submission to that signal.

// registration.component.ts
import { CommonModule } from "@angular/common";
import { Component, signal } from "@angular/core";
import {
  email,
  form,
  FormField,
  required,
  submit,
} from "@angular/forms/signals";
import { RegistrationData } from "./registration.model";

@Component({
  selector: "app-registration",
  imports: [FormField, CommonModule],
  templateUrl: "./registration.html",
  styleUrl: "./registration.css",
})
export class Registration {
  readonly model = signal({
    email: "",
    password: "",
    confirmPassword: "",
    acceptedTerms: false,
  });

  readonly registrationForm = form(this.model, (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",
    });
  });

  async onSubmit(event?: Event) {
    event?.preventDefault();

    await submit(this.registrationForm, (value) => {
      console.log(value());
      // Mock Server Call
      return Promise.resolve([
        {
          kind: "EmailAlreadyExists",
          field: this.registrationForm.email,
          error: { kind: "server", message: "Email already taken" },
        },
      ]);
    });
  }
}

Several design decisions are worth noting.

First, the model signal is defined as read-only. All mutations to the model occur through form bindings, not ad hoc assignments in the component. This keeps the component declarative and avoids the temptation to manipulate form state imperatively.

Second, validation is declared in one place. The schema function describes constraints on the model without introducing control trees, validator arrays, or observable pipelines. Angular takes responsibility for re-running validation whenever the model changes.

Finally, submission logic is explicit. The submit() helper ensures that the form is valid before invoking the callback, and it passes the current model value directly. There is no need to check flags or manually extract values.

Binding the form to the template

With the form defined, the next step is to bind it to the template. Signal Forms provide the [formField] directive, which connects an input element directly to a field in the form schema.





  
@if (registrationForm().errors().length > 0) {
@for (error of registrationForm().errors(); track error.message) {

{{ error.kind }}

}
}
@if ( registrationForm.acceptedTerms().invalid() && registrationForm.acceptedTerms().touched() ) {

{{ registrationForm.acceptedTerms().errors()[0].message }}

}
@if ( registrationForm.confirmPassword().invalid() && registrationForm.confirmPassword().touched() ) {

{{ registrationForm.confirmPassword().errors()[0].message }}

}
@if ( registrationForm.password().invalid() && registrationForm.password().touched() ) {

{{ registrationForm.password().errors()[0].message }}

}
@if ( registrationForm.email().invalid() && registrationForm.email().touched() ) {

{{ registrationForm.email().errors()[0].message }}

}

What stands out here is the absence of indirection. Each input binds directly to a field. Validation state is accessed through signals such as invalid() and touched(). Error messages are read from a structured error object, not reconstructed manually.

This template contains no subscriptions, no async pipes, and no event handlers for value changes. The UI simply reflects the current form state.

Interaction state and user experience

One of the common criticisms of declarative form models is that they obscure user interaction logic. Signal Forms address this directly by exposing interaction metadata as signals.

The touched() signal determines whether a field has been interacted with. By combining it with invalid(), we control when validation messages appear. This logic remains purely declarative: the template describes when errors should be visible, and Angular ensures the signals stay up-to-date.

The disabled state of the submit button is derived from registrationForm.invalid(). There is no need to manually enable or disable it in response to events. If the form becomes valid, the button is enabled automatically.

Why this scales

Even at this early stage, several advantages of a signal-first form model are apparent. The form’s behavior is expressed in terms of state and derivation, not events. The model, validation rules, and UI bindings are clearly separated. There is no duplication of logic between the component and the template.

As the form grows, this structure holds. Additional fields introduce additional schema entries and template bindings, not new subscription logic. Cross-field validation can be added declaratively. Asynchronous validation and persistence can be layered on without rewriting the core model.

Most importantly, the form remains inspectable. At any point during execution, the model signal reflects the current state of the form. Derived state validity, errors, and UI flags can be understood by reading the code, not by tracing runtime behavior.

What we did not solve yet (and why)

At this stage, it would be easy to walk away with the impression that Signal Forms eliminates most of the hard problems associated with form handling. That impression would be misleading. What we have built so far is intentionally incomplete, not because the approach falls short, but because introducing too much too early obscures the value of the underlying model.

One area we have deliberately postponed is cross-field validation that expresses richer business rules. Many real-world forms depend on relationships between fields rather than isolated constraints. Password confirmation is a familiar example, but more complex scenarios quickly arise in enterprise applications. While Signal Forms support these patterns, introducing them before establishing a clear understanding of derived state risks turns validation back into an imperative exercise rather than a declarative one.

We have also avoided asynchronous validation. Server-backed checks introduce latency, partial failure, cancellation, and race conditions. These are not trivial concerns, and treating them casually often leads to subtle bugs and confusing user experiences. Although Signal Forms provide the necessary hooks to model asynchronous behavior, doing so responsibly requires a careful discussion of pending state, effects, and life-cycle boundaries. That discussion belongs in its own article.

Another omission is persistence and synchronization. Many forms need to autosave drafts, synchronize state with local storage, or react to changes by triggering external side effects. These behaviors are not part of the form state itself; they are consequences of state changes. Treating them as such is essential to keeping the architecture comprehensible. Introducing persistence too early would blur the distinction between state and reaction that this article has worked to establish.

Finally, this article has not addressed migration and interoperability. Few teams are starting from a blank slate. Most will adopt Signal Forms incrementally within applications that already rely on reactive forms or template-driven forms. Hybrid approaches, bridging strategies, and gradual refactors are all critical topics, but they presuppose familiarity with both paradigms. Addressing migration before establishing a solid signal-first mental model would undermine that foundation.

These omissions are intentional. A form architecture that tries to do everything at once often ends up doing nothing clearly. By focusing on the core ideas of state, derivation, and declarative validation, we create a base that can absorb additional complexity without collapsing under it.

Signal Forms in the context of Angular’s evolution

To fully appreciate Signal Forms, it helps to step back and view them not as an isolated feature, but as part of a broader shift in Angular’s design philosophy.

For much of its history, Angular emphasized declarative templates paired with imperative coordination in component classes. RxJS became the backbone of that coordination, providing a powerful abstraction for handling asynchronous workflows, user input, and external events. This model scaled well, but it also encouraged developers to express state indirectly through streams and subscriptions.

Signals represent a deliberate recalibration. They re-center Angular’s reactivity model around state and derivation, rather than events and emissions. This shift is visible across the framework: in component inputs, change detection, and now forms. Signal Forms are not an attempt to replace everything that came before; they are an attempt to make the most common use case, modeling and deriving state, simpler and more explicit.

Framed this way, the design of Signal Forms aligns more closely with state-driven form behavior. The requirement to start with a model signal reflects the idea that the state should have a single, inspectable source of truth. Schema-based validation aligns with the notion that constraints are properties of state, not behaviors triggered by events. Field state exposed as signals reinforces the idea that validity, errors, and interaction metadata are derived values that should be read, not managed.

It is also worth noting that Signal Forms do not attempt to abstract away form behavior. They do not hide form state behind opaque classes or life-cycle hooks. They do not require developers to think in terms of control hierarchies or subscription graphs. Instead, they expose form behavior directly, making it easier to reason about how values, validation, and UI feedback relate to one another.

This approach aligns closely with other recent changes in Angular, including the introduction of modern template control flow and a stronger emphasis on explicit data dependencies. Together, these features point toward a framework that favors clarity over indirection and composition over orchestration.

Importantly, Signal Forms are still evolving. Their APIs may change, and their surface area will almost certainly expand. That is precisely why grounding them in first principles matters. Developers who understand why Signal Forms work the way they do will be far better equipped to adapt as the APIs mature.

This article has intentionally avoided duplicating documentation or enumerating every available feature. Instead, it has focused on establishing a conceptual framework that makes the official APIs feel intuitive rather than surprising. When viewed this way, Signal Forms are not a new way to write forms; they are a clearer expression of what forms have always been.

A new way to think about forms

Building the registration form in this article reveals a quiet but important shift. The reduction in complexity does not come from fewer features or simpler requirements. It comes from expressing form behavior in terms of state and derivation rather than orchestration and reaction.

By treating the data model as the single source of truth, validation rules as declarative constraints, and UI behavior as derived from current conditions, much of the coordination logic that typically surrounds forms becomes unnecessary. There are fewer subscriptions to manage, fewer flags to synchronize, and fewer life-cycle concerns to reason about. Form behavior becomes easier to inspect because it is visible directly in the relationships between values.

This approach does not eliminate the hard problems associated with forms. Asynchronous validation, persistence, and interoperability with existing Angular Forms APIs still require careful design. What changes is where that complexity lives. Instead of being interwoven with state representation, those concerns are layered explicitly on top of a clear foundation.

Signal-first forms are not a universal replacement for existing patterns, nor are they a shortcut to simpler applications. They are, however, a strong example of how aligning APIs with first principles can reduce cognitive overhead and improve maintainability over time. For teams building large, state-heavy forms, this alignment can make the difference between code that merely works and code that continues to evolve without friction.

  •  
❌