Enforces Angular 22 fine-grained reactivity using Signals, Signal inputs, and Model primitives.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add PavanAnguluri/angular22-agent-skills --skill ng22-reactivity --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ng22 Reactivity?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/pavananguluri-ng22-reactivity)More formats (shields.io, HTML) on the badges page.
---
name: ng22-reactivity
description: Enforces Angular 22 fine-grained reactivity using Signals, Signal inputs, and Model primitives.
---
# Angular 22 Signals & Reactivity Discipline
You are an expert Angular 22 architect. Treat signals as the default state primitive and keep reactive dependencies explicit, synchronous, and easy to reason about.
## Core Rules
1. Use `signal()` for writable local state and expose readonly views with `asReadonly()` when consumers should not mutate the state.
2. Use `computed()` for every synchronous derivation that the template or another signal depends on.
3. Declare component inputs with `input()` or `input.required()` instead of `@Input()`.
4. Use `model()` for two-way state that must sync between parent and child.
5. Use `effect()` only for side effects that talk to non-reactive APIs; prefer `computed()` when the output is still just data.
6. Read signals before `await` boundaries. Reactive tracking is synchronous, so async gaps break dependency capture.
## Signal Shapes
### Writable state
```typescript
import { signal } from '@angular/core';
const count = signal(0);
count.set(1);
count.update((value) => value + 1);
```
### Readonly exposure
```typescript
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterState {
private readonly _count = signal(0);
readonly count = this._count.asReadonly();
increment(): void {
this._count.update((value) => value + 1);
}
}
```
### Dynamic derivation
```typescript
import { computed, signal } from '@angular/core';
const showCount = signal(false);
const count = signal(0);
const message = computed(() => {
if (showCount()) {
return `The count is ${count()}`;
}
return 'Nothing to see here.';
});
```
## Component Example
```typescript
import { Component, computed, input, model, signal } from '@angular/core';
@Component({
standalone: true,
selector: 'app-user-profile',
template: `
<section>
<h3>{{ fullName() }}</h3>
<p>{{ badgeText() }}</p>
<input [value]="role()" (input)="role.set($any($event.target).value)" />
<button type="button" (click)="isExpanded.update((value) => !value)">
Toggle details
</button>
</section>
`,
})
export class UserProfileComponent {
firstName = input.required<string>();
lastName = input.required<string>();
role = model<string>('Developer');
isExpanded = signal(false);
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
badgeText = computed(() => (this.isExpanded() ? 'Expanded' : 'Collapsed'));
}
```
## Effects and Interop
Use `effect()` when the code needs to update a non-reactive API such as storage, title, analytics, or a logging service. Use `untracked()` when an incidental signal read should not become a dependency.
```typescript
import { effect, untracked } from '@angular/core';
effect(() => {
const user = currentUser();
untracked(() => loggingService.log(`User changed: ${user}`));
});
```
Only use `toSignal()` or `toObservable()` from `@angular/core/rxjs-interop` when bridging asynchronous streams such as HTTP pipelines, WebSocket feeds, or form value streams.
## Anti-Patterns
- Do not store derived data in writable state if it can be expressed as `computed()`.
- Do not mutate nested objects in place and expect the UI to react reliably.
- Do not use signals as a dumping ground for unrelated state.
- Do not call methods from templates when the result can be cached as a signal.
## Review Checklist
- Every input is signal-based.
- Every derived value is a computed signal.
- Side effects are isolated and intentional.
- The template reads data, it does not calculate it.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!