Refactor: Modernize budget viewer state with Signals#173
Open
Virsail wants to merge 6 commits intoitalanta:mainfrom
Open
Refactor: Modernize budget viewer state with Signals#173Virsail wants to merge 6 commits intoitalanta:mainfrom
Virsail wants to merge 6 commits intoitalanta:mainfrom
Conversation
This is to replace constructor injections → inject() convert Observables → signal() replace .subscribe() with effect() + computed() pass signals to child
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.