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>
}
Enter fullscreen mode Exit fullscreen mode

It's straightforward: takes an array and a fallback value, does a bit of computation, and produces some text. The possible outcomes:

  • No videos here
  • 1 Video
  • 2 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
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  });
}
Enter fullscreen mode Exit fullscreen mode
The code above still requires more lines than what you would write with function-based approaches. That extra verbosity comes with notable trade-offs, though:
  • Everything is laid out in a highly transparent manner
  • The code follows a declarative style
On top of that:
  • 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)
For me, these advantages are significant. No two frameworks implement Signals the same way — each has its own take. Let's look at how Solid handles it:
// 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>
}
Enter fullscreen mode Exit fullscreen mode
I genuinely think Solid's way of doing things is excellent too. That said, there are some minor drawbacks worth mentioning.
  • 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.
Just to be clear, this is my personal take. You might actually prefer Solid's style, and that's completely valid — it's a solid piece of tooling. So what draws me to Signals in Angular, and why do I enjoy working with classes in this particular setup? The key is that Angular's signals force you to make derived state explicit. You're required to mark when an input should behave as a signal (assuming that's what you want). The imperative style, like the first example shown earlier in this post, feels out of place when classes live inside a reactive context. If the code you write feels awkward, odds are you'll regret it — whether that regret shows up tomorrow or three months from now. React gives you the option to write imperative code, though it's not something I'd choose. The syntax is compact and pleasant, but it lacks fine-grained reactivity, and Hooks come with their own constraints. I think it's reasonable to say that React's mental model works great until Hooks enter the picture — and they're everywhere in practice. In Solid, you're frequently dealing with a pile of anonymous functions. Props handling can get awkward too — even though props are reactive, you receive them as plain values, and destructuring them feels unintuitive. Angular, on the other hand, offers unambiguous syntax that naturally guides you toward declarative patterns. The price you pay is slightly more code compared to the alternatives. I've held this view for years: writing fewer lines doesn't automatically mean writing better code. Classes are something any student with basic OOP training can grasp. They're straightforward to reason about when used correctly — so let's use them correctly. There's no shortage of class critics, and plenty of their arguments hold weight. But how do I reconcile that with my love for functional programming? If you apply a dose of FP thinking to classes — immutability, treating properties as constants — many of their downsides simply vanish. I'm genuinely glad I won't have to write another 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.

AccademiaDev

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.

Available courses


Photo by Tsvetoslav Hristov on Unsplash