1. Service-Based State vs NgRx — When Should Each Be Used?
Both approaches can manage shared Angular state, but they solve different levels of complexity.
The easiest way to remember:
Small / Medium complexity
↓
Service + Signals / RxJS
Large / Complex state interactions
↓
NgRx
Service-Based State
A service acts as the owner of shared state.
Full Simple Example
import {
Injectable,
signal,
computed
} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CartStateService {
private readonly _items =
signal<string[]>([]);
readonly items =
this._items.asReadonly();
readonly count =
computed(() =>
this._items().length
);
addItem(item: string): void {
this._items.update(items => [
...items,
item
]);
}
removeItem(item: string): void {
this._items.update(items =>
items.filter(x => x !== item)
);
}
clear(): void {
this._items.set([]);
}
}
Components inject:
readonly cart =
inject(CartStateService);
and use:
Cart: {{ cart.count() }}
The architecture is:
CartStateService
Shared Signal
/ | \
/ | \
Header Cart Checkout
When Should You Use Service-Based State?
Use a service with Signals/RxJS when:
- The state is relatively simple.
- Only a few components share the state.
- State transitions are straightforward.
- You don't need formal Action/Reducer architecture.
- You want less boilerplate.
- Your team can easily understand where state changes occur.
Examples:
Current user UI state
Cart count
Theme
Sidebar state
Selected filters
Search criteria
Simple application settings
NgRx State
NgRx introduces a formal state flow:
Component
↓
Action
↓
Reducer
↓
Store
↓
Selector
↓
Component
With APIs:
Component
↓
Action
↓
Effect
↓
ASP.NET Core API
↓
Success Action
↓
Reducer
↓
Store
↓
Selector
↓
Component
When Should You Use NgRx?
NgRx becomes useful when you have:
- Large amounts of shared state.
- Many components interacting with the same state.
- Complex state transitions.
- Complex asynchronous workflows.
- Many related actions.
- Strong requirements for traceability.
- Large teams needing consistent state-management patterns.
- Complex derived state.
- State behavior that needs extensive unit testing.
For example:
Enterprise Application
│
├── Authentication
├── Users
├── Permissions
├── Products
├── Orders
├── Payments
├── Customers
├── Notifications
└── Reports
NgRx can provide a consistent structure for managing these feature states.
Service vs NgRx
| Service + Signals/RxJS | NgRx |
|---|---|
| Simpler | More structured |
| Less boilerplate | More boilerplate/concepts |
| Easy to learn | Higher learning curve |
| Good for small/medium state | Good for complex state |
| Methods modify state | Actions describe changes |
| Service owns state | Store owns managed state |
| Signals/RxJS expose state | Selectors expose state |
| API calls often in services | Effects commonly orchestrate API calls |
| Easy to start | Strong conventions |
Interview Rule
Don't say:
"Large application means NgRx."
Size alone isn't the deciding factor.
A large application with mostly independent features may work well with services.
A better answer is:
Use NgRx when the complexity of shared state, state transitions, asynchronous workflows, debugging, and coordination justifies its additional architecture.
2. Signals vs NgRx — What Is the Difference?
This comparison is important because Signals and NgRx aren't exactly the same category of technology.
Signals
Signals are Angular's reactive primitives.
For example:
count = signal(0);
They allow us to:
count();
read state,
count.set(10);
replace state,
and:
count.update(
value => value + 1
);
update state.
NgRx
NgRx is a broader state-management ecosystem/architecture.
It provides concepts such as:
Store
Action
Reducer
Selector
Effect
Therefore:
Signals
↓
Reactive values
NgRx
↓
Structured state-management architecture
Signal Example
@Injectable({
providedIn: 'root'
})
export class CounterState {
private readonly _count =
signal(0);
readonly count =
this._count.asReadonly();
increment(): void {
this._count.update(
count => count + 1
);
}
}
Flow:
Component
↓
CounterState.increment()
↓
Signal
↓
UI
Very simple.
NgRx Equivalent
The conceptual flow is:
Component
↓
dispatch Increment Action
↓
Reducer
↓
Store
↓
Selector
↓
Component
More structure is involved.
Signals vs NgRx
| Signals | NgRx |
|---|---|
| Angular reactive primitive | State-management ecosystem |
| Stores/reacts to values | Provides structured state architecture |
signal() |
Store |
set() / update() |
Actions + Reducers |
computed() |
Selectors/derived state concepts |
| Simple API | More concepts |
| Excellent for local state | Useful for complex shared state |
| Excellent in state services | Useful for formal enterprise state flows |
| No Action required | Action-driven patterns available |
| No Reducer required | Reducers central to classic Store |
| No Effect required | Effects commonly handle side effects |
The rows aren't strict one-to-one equivalents—they illustrate the different abstraction levels.
Can Signals and NgRx Work Together?
Yes.
Don't think:
Signals VS NgRx
as though selecting one permanently excludes the other.
Modern Angular/NgRx applications can use Signals alongside NgRx patterns.
For example:
Local UI State
↓
Signals
Complex Shared Business State
↓
NgRx
A component could use a local Signal:
isMenuOpen = signal(false);
while the application uses NgRx for:
Products
Orders
Authentication state
Permissions
Complex workflows
That's perfectly reasonable.
3. What State Should Be Kept on the Client vs ASP.NET Core Server?
This is the most important architectural question of the three.
A useful principle is:
Angular may keep UI and working state, but ASP.NET Core/database should remain authoritative for security-sensitive and persistent business data.
Client-Side State
Angular can manage state required primarily for the current user interface.
Examples:
Selected tab
Menu open/closed
Search text
Sort order
Current page number
Selected product
Temporary form state
Loading status
Validation display state
Filters
Modal visibility
For example:
searchText = signal('');
selectedCategory = signal<number | null>(null);
isLoading = signal(false);
currentPage = signal(1);
These are natural client-side state values.
Example
Suppose the Products page has:
Search = "Laptop"
Category = Electronics
Page = 2
Sort = Price Low → High
Angular can manage:
searchText =
signal('Laptop');
selectedCategory =
signal('Electronics');
page =
signal(2);
sort =
signal('priceAsc');
There is generally no reason to send every UI detail to ASP.NET Core for permanent storage.
Server-Side State
ASP.NET Core and its persistence layer should own authoritative business data.
Examples:
Users
Products
Product prices
Inventory
Orders
Payments
Permissions
Roles
Account status
Refresh-token/session records
Business rules
Usually:
Angular
↓
ASP.NET Core
↓
Database
The database is normally the durable source of truth for persistent business data.
Very Important Example: Product Price
Suppose SQL Server contains:
Laptop
Price = ₹75,000
Angular receives:
{
"id": 1,
"name": "Laptop",
"price": 75000
}
Angular may temporarily hold:
product = signal<Product | null>(null);
But this does not mean Angular is the authoritative source.
A user can manipulate browser-side values.
For example, an attacker could attempt to change:
₹75,000
to:
₹1
Therefore checkout should not trust a client-submitted total such as:
{
"productId": 1,
"price": 1
}
as authoritative.
Instead, the client can send the required identifiers/quantities:
{
"productId": 1,
"quantity": 2
}
ASP.NET Core should retrieve authoritative pricing and apply business rules:
Angular
↓
ProductId = 1
Quantity = 2
↓
ASP.NET Core
↓
Retrieve authoritative price
↓
₹75,000
↓
Calculate
75,000 × 2
↓
₹150,000
Authorization Example
Angular may know:
currentUserRole =
signal('Admin');
and use that information to decide whether to display:
Delete Product
button.
That's fine for UI behavior.
But Angular must not be the security authority.
Backend still requires something like:
[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
// Delete...
}
Why?
Because:
Angular UI
↓
User controlled
ASP.NET Core
↓
Security boundary
A malicious user can bypass Angular and call:
DELETE /api/products/10
directly.
Client State Can Be a Copy of Server State
This distinction is important.
Products may exist:
SQL Server
↓
Authoritative Products
and Angular may also contain:
NgRx Store / Signal
↓
Products[]
That's not necessarily duplication in a bad sense.
Angular has a client-side representation/cache of server data.
SQL Server
Authoritative Data
↓
ASP.NET Core
↓
HTTP
↓
Angular
↓
Client-side representation
For example:
products = signal<Product[]>([]);
is perfectly fine.
But the server/database remains authoritative for persistent business truth.
Example: Shopping Cart
Shopping cart design depends on requirements.
For a temporary anonymous cart:
Angular / browser storage
may be sufficient.
For a logged-in user who expects:
Add Laptop on desktop
↓
Open mobile
↓
Laptop still in cart
the cart should normally also be persisted server-side.
Then:
Angular
↓
Client representation
ASP.NET Core + DB
↓
Persistent authoritative cart
Example: Form State
Suppose a user is editing an article:
Title
Content
Category
Tags
Meta Description
Before clicking Save:
Angular Form
↓
Temporary state
After clicking Save:
Angular
↓
POST / PUT
↓
ASP.NET Core
↓
Validation
↓
Database
After successful persistence:
Database
↓
Persistent state
This is a useful distinction:
Before Save
↓
Client working state
After Save
↓
Server persistent state
Client vs Server State Table
| State | Angular | ASP.NET Core / DB |
|---|---|---|
| Menu open/closed | ✓ | |
| Selected tab | ✓ | |
| Search text | ✓ | |
| Current page | ✓ | |
| Loading indicator | ✓ | |
| Temporary form values | ✓ | |
| API product list copy/cache | ✓ | |
| Product authoritative price | ✓ | |
| Inventory | ✓ | |
| Orders | Client copy possible | ✓ |
| Payments | Display/status copy | ✓ |
| User permissions for UI | Copy possible | ✓ authoritative |
| Authorization enforcement | ✓ | |
| Business rules | Some UI assistance | ✓ authoritative |
| Persistent user data | Client copy possible | ✓ |
| Refresh-token/session record | ✓ |
What About localStorage?
localStorage is client-side persistence, not a trusted database.
Good candidates might include non-sensitive preferences such as:
Theme
Language preference
Some UI settings
depending on requirements.
Don't treat:
localStorage
as authoritative for:
Role
Permissions
Product prices
Account balance
Payment status
Security decisions
A user controls their browser environment and can modify client-side storage.
Where Do Signals, Services, NgRx and ASP.NET Core Fit?
A useful architecture is:
ANGULAR
│
┌──────────────┴──────────────┐
│ │
Local UI State Shared State
│ │
Signals Service / NgRx
│ │
└──────────────┬──────────────┘
│
HttpClient
│
▼
ASP.NET Core API
│
Authentication
Authorization
Business Rules
Validation
│
▼
Database
│
▼
Persistent Business
State
Simple Decision Guide
Is the state needed only by one component?
YES
↓
Local Signal
Example:
isModalOpen
Is it shared by several components but relatively simple?
YES
↓
Service + Signals/RxJS
Example:
Cart count
Current UI user state
Are shared state interactions and async workflows complex enough to require stronger conventions?
YES
↓
Consider NgRx
Example:
Products
Orders
Customers
Permissions
Complex workflows
Is it persistent or security/business-critical truth?
YES
↓
ASP.NET Core + Database
Example:
Price
Order
Payment
Inventory
Permissions
Key Points
- Service-based state is simple and works well for many small-to-medium state requirements.
- NgRx provides more formal, predictable state management for complex shared state.
- Application size alone doesn't determine whether NgRx is necessary.
- Signals are reactive primitives; NgRx is a broader state-management ecosystem.
- Signals and NgRx can coexist.
- Keep transient UI state in Angular.
- Angular can cache or represent server data locally.
- Keep authoritative persistent business data on the server/database.
- Never trust client-side state for security decisions.
- ASP.NET Core must enforce authentication, authorization, validation, and business rules.
localStorageis not a trusted server-side database.
Interview Questions & Answers
1. Service-based state vs NgRx?
Service-based state using Signals/RxJS is simpler and suitable when shared state and transitions are relatively straightforward. NgRx is useful when shared state, asynchronous workflows, state transitions, debugging, and team coordination become sufficiently complex to benefit from a formal architecture.
2. Signals vs NgRx?
Signals are Angular reactive primitives for representing and reacting to values. NgRx is a broader state-management ecosystem providing structured patterns such as Store, Actions, Reducers, Selectors, and Effects.
They can be used together.
3. What state should remain in Angular?
Primarily UI and temporary working state:
Search
Filters
Selected tab
Loading
Form state
Menu state
Angular may also hold client-side copies/caches of server data.
4. What state should remain on ASP.NET Core/server?
Authoritative persistent and security-sensitive business state:
Products
Prices
Inventory
Orders
Payments
Permissions
Business rules
usually persisted in a database.
5. Can NgRx Store replace the database?
No.
NgRx manages client-side application state. SQL Server or another backend data store provides durable server-side persistence.
NgRx Store
≠
Database
6. Can Angular store the current user's role?
Yes, Angular can maintain role information for UI behavior, but ASP.NET Core must independently enforce authorization.
[Authorize(Roles = "Admin")]
Interview Quick Answer
Use Angular services with Signals/RxJS when shared state is relatively simple, and consider NgRx when shared state, transitions, and asynchronous workflows require stronger structure and traceability. Signals are reactive primitives, whereas NgRx is a broader state-management architecture. Angular should mainly manage UI/working state and client-side representations of server data, while ASP.NET Core and the database remain authoritative for persistent business data, security, permissions, pricing, orders, and other critical state.