Angular 22+ Migration: $ARGUMENTS

SkillDev tools

Migrate a component or directive to Angular 22+ signal-based patterns

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Angular 22+ Migration: $ARGUMENTS skill

What this skill tells your AI

The instructions your AI receives, as published by sap/fundamental-ngx in .claude/skills/migrate/SKILL.md and read by ahel’s review.

If $ARGUMENTS is empty, ask the user for a component path or folder before proceeding.

Phase 1: Analyze

Read all files in the target path. For each component, directive, or service, scan for migration items below. If a folder is given, process all .ts and .html files within it.

What to migrate

Inputs / Outputs / Models:

  • @Input()input() or input.required()
  • @Input() with setter → input() + linkedSignal() (preferred) or effect() (only for DOM side effects)
  • @Input() + @Output() pair (e.g. value / valueChange) → model()
  • @Output()output()
  • Boolean inputs: add { transform: booleanAttribute }
  • Number inputs: add { transform: numberAttribute }

Host bindings:

  • @HostBinding('class.x')host: { '[class.x]': 'expr()' }
  • @HostBinding('attr.x')host: { '[attr.x]': 'expr()' }
  • @HostBinding('style.x')host: { '[style.x]': 'expr()' }
  • @HostListener('event')host: { '(event)': 'handler($event)' }

Queries:

  • @ViewChild()viewChild() or viewChild.required()
  • @ViewChildren()viewChildren()
  • @ContentChild()contentChild()
  • @ContentChildren()contentChildren()

Template syntax:

  • *ngIf@if
  • *ngFor@for (requires track expression)
  • *ngSwitch / *ngSwitchCase@switch / @case

State management:

  • BehaviorSubject used for local component state → signal()
  • BehaviorSubject + combineLatestcomputed()
  • Subject<void> used only to trigger side effects → effect()
  • effect() that only calls signal.set() for derived state → computed() or linkedSignal
  • Redundant markForCheck() after signal updates → remove
  • Object/array mutation in place + signal.set() with same ref → immutable update with signal.update()

CSS class building:

  • CssClassBuilder + @applyCssClasscomputed() returning class string + host: { '[class]': '_cssClass()' }

Cleanup patterns:

  • Custom DestroyedServiceDestroyRef + takeUntilDestroyed()
  • ngOnDestroy only for destroy$.next() → remove entirely after migration

Decorator cleanup:

  • Remove standalone: true (default since Angular 19)
  • Remove allowSignalWrites option in effect() (the option no longer exists)

Imports:

  • Deprecated *Module classes → individual component/directive imports

Member ordering (ESLint enforced):

  1. Decorated properties (any remaining @Input, @Output, @ViewChild)
  2. Signal inputs/outputs (input(), output(), model())
  3. Public → Protected → Private fields
  4. Constructor
  5. Public → Protected → Private methods

What NOT to migrate

Do NOT convert these — they should stay as-is:

  • RxJS for async operations: HTTP calls, WebSocket streams, timers, interval-based logic → keep as Observable
  • BehaviorSubject with complex operators: switchMap, debounceTime, distinctUntilChanged, combineLatest with async sources → keep as RxJS
  • Plain properties with no reactive consumer: If nothing in template, host binding, computed, or effect reads it → keep as plain property, do not wrap in signal()
  • Signals for internal bookkeeping: One-time flags, cached DOM measurements, counters with no reactive consumer → use plain property

Migration is optional for existing code

The migration targets are recommendations, not mandatory fixes. Present all findings but let the user decide scope. Existing @Input() / @Output() decorators are valid — only migrate them if the user wants to or if the code is being substantially reworked.

Phase 2: Present Plan

Output a structured migration plan:

## Migration Plan: [component name]

### Files affected
- path/to/component.ts
- path/to/component.html
- path/to/component.spec.ts

### Inputs / Outputs
| Current | Migration | Notes |
|---------|-----------|-------|
| `@Input() label: string` | `readonly label = input<string>('')` | |
| `@Input() disabled: boolean` | `readonly disabled = input(false, { transform: booleanAttribute })` | |
| `@Input() value` + `@Output() valueChange` | `readonly value = model<T>()` | Two-way binding |

### Host Bindings
| Current | Migration |
|---------|-----------|
| `@HostBinding('class.is-open')` | `host: { '[class.is-open]': 'isOpen()' }` |

### Template Changes
| Current | Migration |
|---------|-----------|
| `*ngIf="condition"` | `@if (condition) { ... }` |
| `*ngFor="let item of items"` | `@for (item of items; track item.id) { ... }` |

### State Management
| Current | Migration | Reason |
|---------|-----------|--------|
| `BehaviorSubject<boolean>` | `signal(false)` | Local state, no async consumers |

### Skipped (intentionally not migrated)
| Item | Reason |
|------|--------|
| `httpData$` Observable | Async HTTP stream |

### Test Updates
- [ ] Replace `component.input = value` with `fixture.componentRef.setInput('input', value)`
- [ ] Update module imports to individual components
- [ ] Verify existing tests still pass after migration

### Docs Updates
- [ ] Update examples if public API changed (input names, usage patterns)
- [ ] Ensure examples use individual imports, not deprecated modules

### Breaking Changes
- [ ] List any renamed/removed inputs or outputs
- [ ] Draft BREAKING CHANGE footer if needed

Stop here and wait for approval before proceeding.

Phase 3: Execute

After approval:

  1. Apply all migrations from the approved plan
  2. Format code: yarn format
  3. Run build: nx run <library>:build
  4. Run lint: nx run <library>:lint
  5. Run tests: nx run <library>:test --testfile=<spec-file> --skip-nx-cache
  6. Report results and any issues

Signals

GitHub stars
294
Forks
147
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
migrate-sap
Source
github.com/sap/fundamental-ngx