Skip to content

Refactor: Modernize budget viewer state with Signals#172

Open
ahmednule wants to merge 8 commits intoitalanta:mainfrom
ahmednule:feat/refactor-signals-state
Open

Refactor: Modernize budget viewer state with Signals#172
ahmednule wants to merge 8 commits intoitalanta:mainfrom
ahmednule:feat/refactor-signals-state

Conversation

@ahmednule
Copy link

How I Approached the Assignment
I began by analyzing the existing RxJS-based implementation in both SelectBudgetPageComponent (parent) and BudgetTableComponent (child). I identified all observable streams (overview$, sharedBudgets$, allBudgets$) and subscription logic (including SubSink and manual subscribe() calls). Then I:

Replaced constructor-based dependency injection with the modern inject() function.
Converted Observables to Signals using toSignal() from @angular/core/rxjs-interop.
Replaced imperative subscription side effects with declarative effect() blocks.
Used computed() to derive transformed state (e.g., adding endYear to budgets).
Updated the child component to accept plain data inputs instead of Observables, removing all subscription logic.
Modernized the template with @if control flow instead of *ngIf for better signals integration.
The refactor preserved all original functionality while aligning with Angular’s reactive future.

Key Benefits of Signals Over RxJS in This Scenario
Simpler mental model: No need to manage subscriptions, unsubscribing, or async pipes.
Automatic cleanup: Signals and effects clean up automatically when components are destroyed, zero risk of memory leaks.
Fine-grained reactivity: Only the exact parts of the UI that depend on changed signals are updated.
Better performance: Enables zoneless change detection, reducing unnecessary checks.
Type safety & readability: Signals are just functions that return values, no chaining operators or stream composition needed for simple state.
In this UI state management use case (loading and displaying budget lists), Signals are overkill for RxJS, RxJS shines in complex event streams (e.g., autocomplete with debouncing), but is excessive for static or slowly-changing data.

Difference in Coding Styles
RxJS (Reactive/Imperative):
You push data through streams, chain operators (map, switchMap), and imperatively subscribe. You must manually manage lifecycle and cleanup.
Signals (Reactive/Declarative):
You pull values from reactive primitives. State is derived declaratively (computed), and side effects are declared once (effect). Angular handles reactivity automatically.
RxJS = “Tell me when something happens, and I’ll react.”
Signals = “This value changes, automatically update anything that uses it.”

How Signals and Observables Work, Fundamental Difference
Observables (RxJS) are push-based:
They emit values over time to multiple observers. You must subscribe to receive data. They’re lazy, multi-cast (by default), and great for events, HTTP streams, or user input.
Signals (Angular) are pull-based:
They hold a current value that can be read at any time. Consumers pull the value, and Angular tracks dependencies automatically. They’re synchronous, single-value (though they can change), and optimized for component state.
Fundamental difference:
Observables are event streams; Signals are reactive values.
They solve different problems, and can coexist (via toSignal() / toObservable()).

Another Paradigm I’ve Used: React + Redux (with Hooks)
In past projects, I used React with Redux Toolkit + useSelector/useDispatch.

Better:
Centralized store made global state predictable. Time-travel debugging was powerful. Middleware (e.g., for logging or async) was clean.
Worse:
Boilerplate-heavy (actions, reducers, types). Overkill for local component state. Required explicit memoization (useMemo, useCallback) to avoid re-renders.
Compared to Angular Signals:

Signals are more granular, no global store needed for local UI state.
Less boilerplate, no action creators or reducers.
Automatic dependency tracking vs manual selector memoization.
Redux is great for complex cross-cutting state; Signals excel at local and derived component state, which is exactly what this budget table needed.

…e the apps/kujali/src/app with the 2 files as specified in the readme.md
….ts and environment.prod.ts left them as excecuatble data only such that I wont get errors during run time
Removed constructor-based DI and switched to inject()

Converted observable streams (overview$, sharedBudgets$, allBudgets$) into Angular signals and updated component logic accordingly

Replaced manual subscriptions with a declarative signals approach using effect() and computed()

Updated template to pass signals correctly to the child component
Updated inputs to accept signals instead of observables

Removed subscription logic and replaced it with signal-reactive patterns
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