Skip to content

Instantly share code, notes, and snippets.

@miladj3
Last active July 6, 2026 09:31
Show Gist options
  • Select an option

  • Save miladj3/158aaa1c9de370f0ac36a1996739a4cb to your computer and use it in GitHub Desktop.

Select an option

Save miladj3/158aaa1c9de370f0ac36a1996739a4cb to your computer and use it in GitHub Desktop.
Angular Signals Architecture Agent Rule (SKILL.md)
# Angular Signals Best Practices
## Goal
Use Angular Signals as the default reactive state management mechanism for local and application state while preserving RxJS for asynchronous streams and event pipelines.
---
# Core Principles
1. Prefer Signals over BehaviorSubject for state management.
2. Do not replace RxJS with Signals.
3. Treat Signals as state.
4. Treat Observables as asynchronous event streams.
5. Keep components declarative.
6. Avoid unnecessary subscriptions.
7. Prefer immutable state updates.
---
# Use Signals For
✅ Component State
```ts
loading = signal(false);
selectedUser = signal<User | null>(null);
```
---
✅ UI State
- Dialog visibility
- Bottom Sheet
- Sidebar
- Drawer
- Current Tab
- Current Step
- Active Filter
- Selected Item
---
✅ Application State
Examples
- Current User
- Theme
- Language
- Permissions
- Feature Flags
- Shopping Cart
- Selected Organization
---
✅ Derived State
Always use computed instead of getters.
Good
```ts
fullName = computed(() =>
`${firstName()} ${lastName()}`
);
```
Avoid
```ts
get fullName() {
...
}
```
---
✅ Side Effects
Use effect only for side effects.
Examples
- localStorage
- Analytics
- Logging
- Syncing state
- Calling imperative APIs
Avoid using effect to compute values.
---
# Use RxJS For
Always keep Observables for:
- HttpClient
- WebSocket
- SignalR
- SSE
- Router Events
- Form valueChanges
- interval
- timer
- fromEvent
- merge
- combineLatest
- switchMap
- exhaustMap
- concatMap
- retry
- debounceTime
- throttleTime
RxJS is for streams.
Signals are for state.
---
# Bridge Between RxJS and Signals
Whenever an Observable becomes UI State, convert it using toSignal().
Good
```ts
users = toSignal(
this.http.get<User[]>('/users'),
{
initialValue: []
}
);
```
Avoid
```ts
users = signal([]);
this.http.get(...)
.subscribe(v => users.set(v));
```
---
# Store Pattern
Preferred
```ts
@Injectable()
export class UserStore {
readonly user = signal<User | null>(null);
readonly isLoggedIn = computed(() =>
!!this.user()
);
setUser(user: User) {
this.user.set(user);
}
logout() {
this.user.set(null);
}
}
```
Avoid
```ts
private userSubject =
new BehaviorSubject<User | null>(null);
```
unless interoperability with RxJS is required.
---
# State Update Rules
Prefer
```ts
items.update(items => [...items, item]);
```
Instead of
```ts
items().push(item);
```
Never mutate signal values directly.
---
# Component Rules
Components should:
- Read Signals
- Trigger Actions
- Avoid business logic
- Avoid manual subscriptions
Business logic belongs inside:
- Store
- Service
- Signal Store
---
# Computed Rules
Use computed whenever a value depends on another signal.
Never duplicate state.
Good
```ts
total = computed(() =>
items().reduce(...)
);
```
Bad
```ts
total = signal(0);
effect(() => {
total.set(...);
});
```
---
# Effect Rules
Effects should only perform side effects.
Allowed
- console.log
- localStorage
- analytics
- navigation
- imperative APIs
Avoid
- updating unrelated signals
- cascading state updates
- business logic
---
# Template Rules
Prefer reading Signals directly.
```html
{{ user()?.name }}
```
Avoid unnecessary getters.
Avoid wrapping signals inside methods.
---
# Performance
Prefer Signals over getters.
Prefer computed over recalculating values.
Avoid unnecessary effects.
Avoid repeated conversions between Observable and Signal.
---
# Architecture
```
HttpClient
Observable
toSignal()
Signal
computed()
effect()
Template
```
---
# Do Not
❌ Convert every Observable into a Signal.
❌ Replace RxJS completely.
❌ Use Signals as an event bus.
❌ Mutate signal values.
❌ Subscribe inside components unless absolutely necessary.
❌ Store derived state inside another signal.
❌ Use effect instead of computed.
---
# Preferred Stack (Angular 20+)
- Signals for State
- Computed for Derived State
- Effect for Side Effects
- RxJS for Async Streams
- Signal Store for Feature State
- Resources (where appropriate) for async data
- OnPush Change Detection
- Standalone Components
---
# Decision Matrix
| Scenario | Use |
|-----------|-----|
| Component state | Signal |
| Global state | Signal Store |
| Derived value | Computed |
| Side effect | Effect |
| HTTP | Observable |
| WebSocket | Observable |
| SignalR | Observable |
| Router Events | Observable |
| Form Changes | Observable |
| Timer | Observable |
| DOM Events | Observable |
| UI Rendering | Signal |
| Business State | Signal |
| Async Stream | Observable |
---
# Golden Rule
> State lives in Signals.
> Events live in Observables.
> Computations belong in computed().
> Side effects belong in effect().
Never confuse these responsibilities.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment