| 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 defaultsinput.required<T>()for required inputsoutput<T>()replacesEventEmitter- Add
readonlymodifier - 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
- Replace
@Input()→input()orinput.required() - Replace
@Output()→output() - Replace
@ViewChild()→viewChild() - Replace
BehaviorSubject→signal() - Replace derived observables →
computed() - Replace subscriptions for side effects →
effect() - Update templates:
*ngIf→@if,*ngFor→@for - Remove
| asyncpipes (signals auto-unwrap) - Run tests:
npm test