Skip to content

Refactor: Modernize budget viewer state with Signals#173

Open
Virsail wants to merge 6 commits intoitalanta:mainfrom
Virsail:feat/refactor-signals-state
Open

Refactor: Modernize budget viewer state with Signals#173
Virsail wants to merge 6 commits intoitalanta:mainfrom
Virsail:feat/refactor-signals-state

Conversation

@Virsail
Copy link

@Virsail Virsail commented Nov 29, 2025

I began by reviewing the existing components to understand how state flowed from the parent (SelectBudgetPageComponent) to the child (BudgetTableComponent). The original implementation used multiple RxJS streams and subscriptions to manage UI data. My first step was to replace constructor injection with Angular’s modern inject() function to simplify dependency handling.

Next, I migrated the observable streams (overview$, sharedBudgets$, allBudgets$) into signal()-based state. Instead of manually subscribing to each stream, I used effect() to declaratively react to changes and update the signals accordingly. This removed the need for explicit subscription and cleanup logic. I then introduced a computed() signal to combine state where needed, and updated the templates to receive and render signal values directly.

Finally, I refactored the child component to accept signal-based inputs using input(), removing all RxJS-based subscriptions and async pipes. This resulted in a cleaner and more predictable data flow between parent and child.

Key benefits of using Signals over the previous RxJS approach

Synchronous and predictable: Signals update synchronously and immediately, making UI state easier to reason about.

No subscriptions required: Signals eliminate the need for manual .subscribe() and .unsubscribe(), removing a common source of memory leaks.

Fine-grained change detection: Signals update only the parts of the template that depend on them, improving UI performance.

Simpler templates: Removes async pipes and reduces observable boilerplate.

Easier debugging: Signals expose their current value directly, unlike observables where you often have to subscribe to inspect state.

Overall, Signals provide a more lightweight, declarative, and UI-friendly approach for component-level state.

Difference in coding styles: Signals vs RxJS

Signals are declarative and stateful.
You read them like variables (signal()) and Angular reacts automatically. The code feels closer to traditional reactive UI state.

RxJS is stream-oriented and event-driven.
You build pipelines, transform values, and subscribe to receive changes. This is powerful but more complex for simple UI state.

Signals encourage “pull-based” reactivity where components access the latest value when needed.

RxJS uses “push-based” reactivity where values are pushed through streams regardless of whether the UI needs them.

For UI components that need fast, localized updates, Signals are simpler and more ergonomic. For complex async operations or multi-stage data transformations, RxJS is still the better tool.

Fundamental difference:
Signals store state, while Observables emit events. Signals are pull-based state holders; Observables are push-based data streams. They complement each other but serve different purposes.

Another paradigm I’ve used and comparison

A comparable paradigm I’ve used is React’s useState() and useEffect() hooks.

Better:

React’s useState() is conceptually similar to Signals—local state that triggers a UI update.

React’s developer ecosystem is mature, and state patterns like useMemo/useCallback parallel Angular’s computed/effect.

Worse:

React hooks depend on dependency arrays, which can easily introduce bugs if not managed correctly.

Re-renders in React are more frequent and less fine-grained than Angular Signals.

Signals provide clearer, automatic dependency tracking with no dependency arrays required.

Angular Signals essentially solve many of the root issues behind React’s re-render model.

This is to Replace @input() observable$!: Observable<...>
→ @input() data = input.required<any>();

Remove subscriptions and Use signals directly in template
This is to replace constructor injections → inject()

convert Observables → signal()

replace .subscribe() with effect() + computed()

pass signals to child
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant