Facading your Signals

Typically, enterprise development is slow, and as an enterprise developer, I’m pretty slow as well. The Angular Signals hype has been all the rage for a few months now, and while yes, I have been following along, I’ve done my best to sit back and watch the hype, waiting for it to die down. I’m still waiting to start writing standalone components and pipes in my work project! I’d done my best to reserve judgment on Signals since I’ve been consistently talking about keeping business logic (and general logic) out of components and moving those things into Services, Pipes, and Directives while reserving components as a way to handle views and being a “melting pot” for all of those other classes.

It wasn’t until last weekend that I decided to finally update one of my pet projects to Angular 16 and compare the differences between @ngrx/component-store and signals. I know that the NgRx team is actively working on a signal-store, but I did want to see if I could properly facade logic with signals and get a similar look and feel without waiting for a new package.

What is a facade?

The word “facade” comes from the French word “façade,” which means “frontage” or “face.” It originally referred to architecture, specifically the front of a building facing the street; that public-facing portion was far simpler and hid the more complex architecture inside or on the back containing complex columns, windows, and other aesthetic features.

In the modern sense, a facade is simple and easy to look at, hiding the more complex and unappealing structure on the other side. Usually, it describes someone presenting a facade of happiness while they are depressed or sad inside.

In the software development world, we use a facade to provide a simple interface to a consumer or as a public API while hiding complex logic in some other system or class. In Angular, funny enough, we deal with a lot of facades. Signals are a facade. Most public-facing APIs are facades.

When developing, I like to facade my business logic in services, namely “facade services,” which act as a middle-man to services that make HTTP requests or handle complex functionality. I use @ngrx/component-store for this purpose to great effect.

Using Component Stores as Facades

Component-Stores were created as a substitution for the “Service with a Subject” approach. With the component store, you typically provide the Store to a component and isolate some local state to that component and its children. When the component is destroyed, the store is also cleaned up. If you’ve never used component-store, I’d advise you to check out this article for the differences between using @ngrx/store and @ngrx/component-store.

Todo-List using component-store facades

Here, I have a pretty simple To-Do List component where I provide the TodoListStore to a feature page. I only reference the store in the template, and I’ve removed all of the logic from my component and hidden it behind the store’s actions.

https://medium.com/media/51caaeab23c4bbb6cd3b631354ea3a0f/href

The consumer of the component store will have a single function to add a new task to the state. This is definitely “overkill” since this effect isn’t calling a backend, but imagine it did. It would have an extra operator to make the backend request, most likely calling a service class that handles HTTP requests (we wouldn’t want that in our facade service).

https://medium.com/media/6bcc15259f3f80111b0c7c48f0343a25/href

This store would have functions for other CRUD operations (addTask, deleteTask andupdateTask), but it would also contain a single source of truth. This keeps our components clean and allows us to focus explicitly on component testing, keeps our HTTP-using services free of business logic, and lets our state only be affected by functionality within the store.

This has been a staple architectural pattern I’ve used since the release of Component-Store, so when Signals were announced and most of the examples shown had the signal functionality coming from the component class, I was pretty disappointed. It wasn’t until I decided to pull down Angular 16 and try it for myself that I could pass judgment.

Using Signals as Facades

I decided to do a one-to-one conversion, maintaining feature parity with the component store and simply pulling some Signals into a facade service. Typically with a component store, you define the state based on an interface you define. The task service that I use has a simple state:

export interface TodoListState {
tasks: Task[]
}

Implementing that interface to a Signal was an easy task:

private state = signal<TodoListState>({
tasks: []
});

The signal function is generic, just like the component store class, so I can define exactly what my state model will look like.

The next thing of note was reading from the state. Generally, in a component-store, you create a selector to read from slices of state. This is one of the most powerful features of ngrx, being able to create smaller slices of state to read from and reference those reactively. If I want a list of completed tasks, I can create a selector that only filters down to those values. If I were to jump to signals, I’d need a similar feature.

Luckily there is!

readonly tasks = computed(() => this.state().tasks);

If I want to reference the list of tasks, I can create a property using the computed function that allows me to create a computed signal whose value comes from some derived value; in this case, I want the raw value of tasks. But going back to that example I mentioned earlier, creating a selector for completed tasks: this.state().tasks returns an array, I can still perform array functions here and filter down to just completed tasks and create a computed property that returns that value.

Finally, the effect functionality is the most important part of the facade. I wanted to ensure it was still easy to update the state in one service and remove that logic from the component.

https://medium.com/media/5e1bbffef7fd8c763276e451f6dd812f/href

Signals come with a few functions that allow you to update their values. Update, Mutate, and Set. Here I’m using update, but I could’ve just as easily used mutate and modified the state’s value in line, but in this example, I’m maintaining the pattern that I would generally use in the component store and returning the value of the state as I update it. When I create a new task, I add the description, default status, and a new ID to the top of the array along with the other tasks.

With Signals, I don’t have to worry about unpacking the observable when it is passed into the effect; I immediately have access to the value and can begin performing operations on it. There is a downside here, but for now, it definitely gets the job done, and if I needed to perform a backend request, I still could. That is one of the beauties of Signals; I can still use RxJS with my signals.

So is the switch worth it?

I think so. This still isn’t a state management system; the Angular team has said several times in previous posts that there is nothing in the Angular ecosystem by default that is labeled as state management. RxJS isn’t a state management library, either. I don’t think Signals are trying to fill that void. I’m sure the Signal Store will make much of what I’m doing cleaner, but at this point, Signal Services look far cleaner than Service with a Subject. Unit testing your services with signals is much easier than working with the property functions you get with NgRX, but if you know how to test those already, you’re not losing much in that department.

With Signals being an Angular primitive, you also get the added benefit of not needing to add a third-party package to your project if you want a way to handle state OR to do facades. Feel free to look at the entire to-do list repo and let me know what you think about the pattern.

Facading your Signals was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *