Claude Code Plugins

Community-maintained marketplace

Feedback

migrate-to-signals

@alysson-souza/hnews
0
0

Convert RxJS observables or decorator-based code to Angular signals. Use when refactoring to signals, converting @Input/@Output to signal functions, or replacing BehaviorSubject with signal().

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name migrate-to-signals
description Convert RxJS observables or decorator-based code to Angular signals. Use when refactoring to signals, converting @Input/@Output to signal functions, or replacing BehaviorSubject with signal().

Migrate to Signals Skill

Convert legacy Angular patterns to modern signal-based APIs.

Input/Output Decorators → Signal Functions

Before

@Input() name: string = '';
@Input() count!: number;
@Output() clicked = new EventEmitter<void>();

After

readonly name = input<string>('');
readonly count = input.required<number>();
readonly clicked = output<void>();

Key Changes

  • input() for optional inputs with defaults
  • input.required<T>() for required inputs
  • output<T>() replaces EventEmitter
  • Add readonly modifier
  • Import from @angular/core: input, output

ViewChild/ContentChild → Signal Queries

Before

@ViewChild('myRef') myRef!: ElementRef;
@ViewChild(ChildComponent) child!: ChildComponent;

After

readonly myRef = viewChild<ElementRef>('myRef');
readonly child = viewChild(ChildComponent);
// Access: this.myRef()?.nativeElement

BehaviorSubject → signal()

Before

private items$ = new BehaviorSubject<Item[]>([]);
items = this.items$.asObservable();

addItem(item: Item) {
  this.items$.next([...this.items$.value, item]);
}

After

readonly items = signal<Item[]>([]);

addItem(item: Item) {
  this.items.update(current => [...current, item]);
}

Observable Derived State → computed()

Before

totalCount$ = this.items$.pipe(map((items) => items.length));

After

readonly totalCount = computed(() => this.items().length);

Subscriptions → effect()

Before

ngOnInit() {
  this.items$.subscribe(items => {
    console.log('Items changed:', items);
  });
}

After

constructor() {
  effect(() => {
    console.log('Items changed:', this.items());
  });
}

Observable to Signal (interop)

import { toSignal } from '@angular/core/rxjs-interop';

// Convert observable to signal
readonly data = toSignal(this.http.get<Data[]>('/api/data'), { initialValue: [] });

Template Changes

Before

<div *ngIf="loading">Loading...</div>
<div *ngFor="let item of items$ | async">{{ item.name }}</div>

After

@if (loading()) {
<div>Loading...</div>
} @for (item of items(); track item.id) {
<div>{{ item.name }}</div>
}

Testing Signal Inputs

// Set input values in tests
fixture.componentRef.setInput('count', 5);
fixture.detectChanges();

Checklist

  1. Replace @Input()input() or input.required()
  2. Replace @Output()output()
  3. Replace @ViewChild()viewChild()
  4. Replace BehaviorSubjectsignal()
  5. Replace derived observables → computed()
  6. Replace subscriptions for side effects → effect()
  7. Update templates: *ngIf@if, *ngFor@for
  8. Remove | async pipes (signals auto-unwrap)
  9. Run tests: npm test