Declarative Code and Functional Roots
Great articles about React, Solid, and Angular have been circulating lately, written by some genuinely sharp developers. I'm a fan of both the frameworks and the people behind them. Sure, I'm not sold on Hooks—but that's beside the point.
You've probably caught wind of the major development: Signals are making their way into Angular. There are plenty of reasons to be excited, but the one I want to dig into here is declarative code.
The starting point for this discussion is a React component example shared by Dan Abramov:
// React
function VideoList({ videos, emptyHeading }) {
const count = videos.length;
let heading = emptyHeading;
if (count > 0) {
const noun = count > 1 ? 'Videos' : 'Video';
heading = count + ' ' + noun;
}
return <h1>{heading}</h1>
}
It's straightforward: takes an array and a fallback value, does a bit of computation, and produces some text. The possible outcomes:
No videos here1 Video2 Videos
Those are all the cases we're dealing with.
Now, I have a soft spot for Functional Programming, and React's design leans into that functional style.
f(state) = UI
I'll skip the Hooks debate here—it's not what I'm after.
What matters is that Functional Programming falls under the umbrella of Declarative Programming. And I'm all in on declarative code. But that particular component example? It doesn't sit right with me.
That snippet was meant as a simple illustration, sure. Yet I keep running into this kind of code in real projects, so it's a good place to launch into why declarations speak to me and why Signals in Angular have me excited.
This article assumes you're familiar with Signals fundamentals (and won't hurt if you know a touch of React for the comparisons!).
Functional Means Declarative
Back to the component:
// React
function VideoList({ videos, emptyHeading }) {
const count = videos.length;
let heading = emptyHeading;
if (count > 0) {
const noun = count > 1 ? 'Videos' : 'Video';
heading = count + ' ' + noun;
}
return <h1>{heading}</h1>
}
What bothers me? The logic here is imperative. With a tiny component like this, it's fine. But that's not what complex codebases look like in practice.
To figure out what heading might be, I need to trace through the entire component.
Pure functional programming doesn't have variables—only constants. JavaScript does let us reassign, and we don't have to adhere strictly to FP rules to write decent code. Even so, it's a solid habit to follow in plenty of scenarios.
Here's how the declarative version would look:
// React (declarative)
function VideoList({ videos, emptyHeading }) {
const count = videos.length;
const heading = count > 0
? count + ' ' + (count > 1 ? 'Videos' : 'Video')
: emptyHeading;
return <h1>{heading}</h1>
}
Each constant carries its own complete logic. When I spot const heading = this_thing, my brain reads it as "Here's what heading represents." No need to look elsewhere in the component.
The downside? Ternary operators can get a bit gnarly to read and awkward to write. Intermediate variables (like noun in the prior example) are also tough to work in without resorting to IIFEs or other strange patterns.
Still, that's a minor trade-off for what we gain. At a glance, I instantly get what heading is. That declaration holds the complete recipe for producing that value. I can skip reading the rest of the component.
This hits home for me as a consultant: I need to glance at code and quickly get its logic. Why? Because I can't dedicate weeks to understanding an entire project—and neither can the client. Time is money.
The same logic applies to teams. If I can grasp something immediately, the people maintaining it probably can too.
Working with Classes
A class brings a different mental model than pure functions. Instead of a flow of instructions, you get Properties and Methods grouped together.
That earlier imperative React component doesn't translate directly to a class-based approach. You'd have to bury the logic inside a method, and it would look awkward and ugly:
// Angular (kinda, you get the point)
class VideoListComponent {
@Input() videos;
@Input() emptyHeading;
count = 0;
heading = '';
ngOnChanges(changes) {
this.count = changes.videos?.currentValue.length;
this.heading = this.count > 0
? this.count + ' ' + (this.count > 1 ? 'Videos' : 'Video')
: this.emptyHeading;
}
}
Honestly? That's rough. Compare it to how clean those React examples looked. I have zero interest in Lifecycle methods. I just want to express derived values: I want to write a recipe.
With classes, getters are the tool for declaring derived state:
class VideoListComponent {
@Input() videos = [];
@Input() emptyHeading = '';
get count() {
return this.videos.length;
}
get heading() {
if (this.count > 0) {
const noun = this.count > 1 ? 'Videos' : 'Video';
return this.count + ' ' + noun;
}
return this.emptyHeading;
}
}
That's far easier to read and reason about. For these straightforward use-cases, it's my go-to suggestion.
One nice thing about getters being functions: you can comfortably use intermediate variables (noun) with a sprinkle of imperative logic that stays contained rather than leaking across the whole component.
The verbosity is a drawback, no question. Plus there's the question of whether the framework notices updates. Those getters lean on Change Detection to re-evaluate, and without a reliable way to detect that a mutable property shifted, they might run far more often than needed.
Signals are poised to change that picture entirely.
Understanding Angular's Signal Approach
class VideoListComponent {
// I think this is what we'll end up with, reactive
// inputs as signals with default values, or something like that!
videos = input([]);
emptyHeading = input('');
count = computed(() => this.videos().length);
heading = computed(() => {
if (this.count() > 0) {
const noun = this.count() > 1 ? 'Videos' : 'Video';
return this.count() + ' ' + noun;
}
return this.emptyHeading();
});
}
- Everything is laid out in a highly transparent manner
- The code follows a declarative style
- Performance comes out of the box — just like with any other Signal implementation. I see this as the direction all frameworks are heading. The mentality of "optimize later if needed" doesn't sit well with me. In a large codebase, it's easy to underestimate how expensive a given calculation might become a few months down the road when more code has piled up. Tracing the root cause at that point becomes a painful exercise.
- We finally get to drop
zone.js(which is a relief)
// Solid
function VideoList(props) {
const count = () => props.videos.length;
const heading = () => {
if (count() > 0) {
const noun = count() > 1 ? "Videos" : "Video";
return count() + " " + noun;
}
return props.emptyHeading;
}
return <h1>{heading()}</h1>
}
- Why do arrow functions show up all over the place? You need prior knowledge that in Solid, reactivity is achieved by turning a variable into a function. That distinction isn't immediately obvious when you're scanning the code.
- A function call doesn't tell you whether it's computing derived state or responding to an event. Both look identical, so you end up reading through the logic to figure out what's happening — assuming the variable name isn't descriptive enough to save you.
ngOnChanges in my life. No more input setters to populate BehaviorSubjects either!
PS. Please don't interpret any of this as criticism toward React or Solid.
Check Out AccademiaDev: Text-Focused Web Development Training
My philosophy is to offer focused, high-quality material that skips the filler typical of traditional textbooks. These interactive online courses — built on my extensive consulting and training background — teach through written explanations, code examples, and quizzes. It's a practical, streamlined way to learn without the fluff.
Photo by Tsvetoslav Hristov on Unsplash


