Last active
July 6, 2026 09:31
-
-
Save miladj3/158aaa1c9de370f0ac36a1996739a4cb to your computer and use it in GitHub Desktop.
Angular Signals Architecture Agent Rule (SKILL.md)
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
| # 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