ANGULAR START v19 has launched! ... Get 25% off LEARN MORE

đź‘‹ Hey there!

I'm Josh Morony, and this website is a collection of my free content centered around creating modern Angular applications using the latest Angular features, and concepts like reactive and declarative code.

Tutorial hero
Lesson icon

The Problem with Writing "Fully" Declarative Code in Angular

Originally published May 10, 2023 Time 6 mins

In this blog post, we’ll discuss the balance between declarative and imperative code in Angular. While I often advocate for the benefits of declarative code, you may have noticed that the code I write is not “fully declarative”. In fact, in the Angular space, it’s rare to find anyone who codes “fully declarative”. I’m not even sure that’s entirely possible.

This article is based on the following video:

Why Isn’t My Code Fully Declarative?

Usually, the reason the code I write is not fully declarative is because I often do this:

Image showing next subjects

I next subjects. Can you believe that? The problem with nexting subjects or setting signals, and why it is imperative not declarative, is because we don’t know what this thing is or how it changes over time just by looking at its declaration.

To understand what this is, I need to go searching through this component. I can then see that this subject is being nexted in response to the next and previous page buttons being clicked.

Image showing next buttons

Despite this, I’m still composing a stream that is created by nexting this subject into my stream of data. Overall, this is quite a declarative approach, with just a tiny bit of “cheating”.

Image showing code

Going Full Declarative

However, we don’t need to “cheat” like this. We could go full declarative mode. We don’t need to create a subject that we next in response to buttons being clicked because we can just compose the DOM events from those button clicks directly.

So, we end up with something like this:

Image showing current page code

We can delete the subject and the handlers we were using to next it, and now we just have this one declaration that defines the behavior of what the currentPage should be.

We create observables using fromEvent for our next and prev buttons in the template. To make my life a bit easier, I’m mapping these to 1 and -1 to indicate how they should affect the current page number.

The scan operator is similar to a reduce and basically allows us to collect whatever values have ever been emitted on this stream. We start with an initial value of 1, and then each time either of our buttons are clicked we add it to the initial value.

Image showing value code

So, if next was clicked it will add one, and if prev was clicked it will subtract one. We also add this check to make sure we don’t go below 0.

Image showing code

You might also notice that we start with this timer() observable stream and then immediately switch away from it. This is a nice little hack to deal with the fact that the nextPageButton and prevPageButton aren’t defined immediately - this delays the evaluation of our buttons until after the template has been initialised.

Image showing undefined code

Expanding This Strategy

But what if we try to expand this strategy to the rest of the application? What if we have a service that is sharing data throughout the application - and we want to be able to modify that data throughout the application - basic CRUD operation stuff.

As an example, we will use this service from the example signals application I created for a previous video. This one doesn’t next subjects, it sets signals, but it’s the same idea.

Image showing add method code

We have this add method that can be called in the service, and calling that method will modify this signal, and that is where all the data we want to share throughout the app is stored.

But we know we can’t next subjects or set signals because that would be naughty. We might try to do the same thing again here - rather than imperatively setting a signal or subject by calling the add method, we could just use the event directly from wherever the add action is originating, just like we did with the pagination example.

Image showing example code

The problem here is that the action is going to be triggered from within a component, or it might be triggered from multiple different components, and we want that stream in our service.

Image showing diagram of components

So our service would need to do something like get references to every component in the application that wants to trigger a change and compose a stream of whatever is triggering those changes. But those components might not even exist yet.

The Imperative Detour

At this point, we give up on our dreams of having a fully declarative codebase and just stick with our simple, but imperative, add method that nexts a subject or sets a signal.

This is a nice pattern in my opinion, and it is only a very minor concession. Although technically it is imperative, it’s not like we are giving up on this whole declarative idea. Nexting a subject or setting a signal in this case is like this little imperative detour that makes things easier and then we get right back onto the declarative highway.

Image showing diagram of imperative detour

So, we establish that a little bit of imperative code can be OK - even required in some cases. Now that we know that, do we still keep this fully declarative pagination approach at the component level just because we can in this case?

Maybe. That’s up to you to decide. In most cases, I prefer to go with the little imperative shortcut. I think it simplifies the mental model a great deal. But I think both approaches are fine, and I might even end up changing my mind on the subject.

If I could make my code 100% declarative I probably would - even if it were harder in some cases, but since I can’t, I feel like I’ve got a bit more freedom in determining where I should draw the imperative line.

Learn to build modern Angular apps with my course