top of page

A Love Letter to Angular

  • Writer: Sam Scholefield
    Sam Scholefield
  • Mar 4
  • 7 min read

Or: How I Learned to Stop Worrying and Love the Signal


Consider the ship. Angular is not, obviously, a ship. But it has carried enterprise applications across the dark void of production for the better part of a decade with roughly the same grace. Vast, opinionated, occasionally maddening, and stubbornly resistant to sinking. It has not always been loved. It has, at various points in its long and storied career, been actively despised, sometimes by the very people who depended on it most, which is, when you think about it, the most human reaction imaginable.


I have been one of those people. I have sat monitoring a complex Friday afternoon deploy, staring at a ChangeDetectorRef.detectChanges() call wedged into a component like a shim holding up a cathedral, and I have thought: this is not how things should be. I have watched Zone.js, that monkey-patching Prometheus, hook its hairy fingers into every browser event and I have thought: and yet, here we are.


But this is a love letter. And love letters, the good ones at least, are not written to the perfect and the unblemished. They are written to the thing that drove you half mad and then, one unremarkable Tuesday morning, showed you something so beautiful that the entire history of grievance simply... evaporated.


That unremarkable Tuesday morning, for me, was signals.

Abstract image: On the left, chaotic gears and cables in red and blue; on the right, an ordered network in blue and pink. In the center, a bridge motif.

The Old World, Briefly Mourned

To understand why Angular's signal-based reactivity feels less like an incremental improvement and more like the moment a civilisation discovers fire for the second time (but properly, this time, with safety protocols), you have to understand what came before. And what came before was, to put it charitably, a lot.


Reactive Forms served us with the grim determination of a seasoned engineer on a project they privately suspected was held together with hope and zip ties. You would define your FormGroup. You would define your FormControl instances within it, each one a tiny independent state machine with its own lifecycle and its own quiet resentment at not being properly typed. You would then manually synchronise these controls with your data model through subscriptions, valueChanges pipes, patchValue incantations, the occasional ritual sacrifice of a console.log to find out what had gone wrong.


The types were, and I say this with the affection one reserves for a beloved but unreliable dog, a lie. And ControlValueAccessor, that fearsome interface for custom form controls, demanded you implement four methods and understand a registration process that felt like it had been designed by a committee who had recently discovered dependency injection and were extremely pleased with themselves.


It worked. Everything works, technically, right up until the moment you need it to work well.


The Signal Arrives

Angular introduced signals in version 16, and the initial response from the community was a mixture of cautious optimism and the weary suspicion of people who have been told "this changes everything" before. Signals were, on the surface, simple: a reactive primitive. A box that holds a value and tells interested parties when that value changes. That's it. That's the whole thing.


But "that's the whole thing" is what people said about general relativity too, and look where that went.


What signals actually represented was a philosophical realignment of the entire framework. Angular had always detected changes by checking everything. A button click, a timer tick, a single property change in a distant service: check the whole tree. Always the whole tree. At scale, this was somewhat like searching every room in a hotel because someone on the third floor sneezed.


The performance cost was exactly what you'd expect: applications with hundreds of components would dutifully re-examine every single binding on every single cycle, burning through CPU time on comparisons that would almost always conclude that nothing had changed. You could mitigate it with OnPush strategies and careful architecture, but the fundamental problem remained: the framework had no way of knowing what had changed and so, like a somewhat paranoid detective, it was obliged to suspect everything.


Signals replaced this with fine-grained reactivity. The signal knows its dependents. The dependents know the signal. When a value changes, only the things that actually care about that value are notified. No tree-walking. No Zone.js. No monkey-patching of browser primitives. Just a clean, direct, almost absurdly elegant chain of cause and effect.


And then they kept going.


computed() gave us derived state that recalculates lazily and caches its results. Simple and exactly right. effect() gave us a way to bridge the reactive world into the imperative one, with appropriate warnings that it should be used sparingly, like handing a toddler a permanent marker: technically possible, probably best supervised. linkedSignal() solved the awkward middle ground between read-only derivation and writable state. The number of hacks these three primitives eliminated from codebases across the world is, I suspect, incalculable.


And then, with version 19.2, came httpResource.


httpResource, or: The Reactive Graph Gets an Internet Connection

There is a particular kind of boilerplate that haunts Angular services. You know the one. The loading/error/success triptych. You make an HTTP call. You need a property for the data. Another for the loading state. Another for the error. You write this pattern once, twice, a hundred times, each time slightly differently, each time with the nagging certainty that there must be a better way.


httpResource is that better way. It is a reactive wrapper around HttpClient that gives you, in one declarative statement, a resource whose value, status, error, and loading state are all signals. You point it at a URL, or better yet, at a function that computes a URL from other signals, and it fetches. When those source signals change, it re-fetches. If a request is already in flight, it cancels it first. It does all of this with the quiet competence of someone who has been asked to do a job slightly below their capabilities and is, therefore, faintly bored by it.


Two lines of code. Reactive data fetching. Automatic cancellation. Type-safe responses. It does one thing. It does it beautifully. And it isn't trying to replace HttpClient for mutations, and it says so, plainly, in the documentation, which is the kind of honest self-assessment that makes you trust an API more, not less.


Signal Forms, or: The Cathedral Rebuilt

httpResource proved Angular's reactivity story was real. Signal Forms, shipped experimentally with Angular 21, proved it had ambitions.


The form is the signal. The signal is the data model. There is no FormGroup. There is no FormControl. There is no parallel state universe that you must keep synchronised with your actual domain objects through subscription machinery and good intentions.


Instead, a form() function takes your signal and returns a structure where every property carries its own value, dirty state, touched state, validity, and errors, all as signals, all reactive, all properly typed because the types are inferred directly from your data.


Validation is a schema function that receives a typed path tree. You declare what each field requires. Custom validators slot in with the same pattern. Cross-field validation works. Conditional validation works. ControlValueAccessor, that four-method interface that launched a thousand confused Stack Overflow questions, is replaced with something dramatically simpler.


But the detail that made me actually stop and stare, the detail that elevated this from "nice improvement" to "someone sat down and rethought the problem from first principles," is the two-way binding. The signal you pass to form() is the owner of the state. When the user types into an input, the signal updates. When you programmatically update the signal, the input updates. One source of truth, and it is the thing you were already going to create anyway. The form doesn't manage your state; it reflects it.


For those with existing Reactive Forms codebases, and that is effectively everyone, there is compatForm, a migration bridge that lets you introduce Signal Forms incrementally without rewriting the world. This is the behaviour of a framework that has learned, perhaps after some difficult conversations, that the path of gentle migration is the only path that actually works.


Zoneless, or: The Monkey Unchained

Every journey needs an ending, and this one ends where it logically must: with the elimination of Zone.js itself. Angular 21 made zoneless change detection the default path. Zone.js is no longer necessary. The signals themselves are the change detection mechanism. When a signal that a template reads is updated, that template is scheduled for re-render. Nothing else. No tree-walking. No monkey-patching. No thirty kilobytes of runtime overhead that existed solely to answer the question "did something happen?"


The result is smaller bundles, faster rendering, cleaner stack traces, and the private satisfaction of knowing that your framework is no longer covertly modifying window.Promise behind your back.


The Love Letter, Finally

I said at the beginning that love letters have conditions. Angular met them. It drove me mad. It drove many of us mad. Mad at the ceremony, the boilerplate, the decorators that were really just metadata, the modules that were really just organisational units with dependency injection side effects.


But Angular did something sadly rare in this industry: it took the complaints, years of them, some politely worded, much not, and it listened. Not in the superficial way of slapping a new API on top of old machinery, but in the deep way, the way that requires you to rethink your foundational assumptions and rebuild from the ground up. Signals aren't a feature. They're a new foundation.


And every layer of the framework now speaks the same reactive language. The data model is a signal. The derived state is a computed signal. The HTTP fetch is a resource backed by signals. The form is a reflection of a signal. The change detection is triggered by signals. It is, for the first time in Angular's history, a coherent reactive story from the network boundary all the way down to the DOM.


That alone would have been enough.


Most frameworks accrete features like sediment, layer upon layer, each reasonable in isolation, collectively forming a geology that requires specialised knowledge to excavate. Angular did something harder than adding another layer. It stripped back to bedrock, without breaking the millions of applications already built on the old ground. Signals are not a new feature on top of old ones. They are the thing that makes the old ones unnecessary.


Whatever framework you call home, it's hard not to respect the ambition of it. The sheer stubborn patience required to migrate an entire ecosystem without leaving anyone behind. The honesty of marking things experimental when they're experimental. The dignity of saying "this API is not designed for that" instead of letting it quietly fail.


So: to Angular, and to the people who build it. This is my love letter. You drove me mad. You showed me something beautiful.


I am, against all prior evidence and expectation, genuinely excited to see what you do next.

bottom of page