input(), output(), viewChild(), viewChildren(), contentChild(), contentChildren(), outputFromObservable(), and outputToObservable() are in the stable status in Angular 19. Production applications can start using the Angular schematics to convert from the decorators to the signals.
Angular 19 combines the signal input, output, and query migrations into a single signal migration. Developers can open a terminal, execute the schematic, and select one or all migrations from the text menu.
Let's do a step-by-step migration in this blog post.
You can find the decorator version in this feature branch: https://github.com/railsstudent/ithome2024-demos/tree/refactor/day40-decorators/projects/day40-migration-schematic-demo
Example 1: Migration to signal inputs and new output
@Component({
 selector: 'app-some',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div>
     <p>bgColor: {{ bgColor }}</p>
     <p>name: {{ name }}</p>
   </div>
 `,
})
export class SomeComponent {
 @Input({ required: true, alias: 'backgroundColor'}) bgColor!: string;
 @Input({ transform: (x: string) => x.toLocaleUpperCase() }) name: string = 'input decorator';
}
The SomeComponent component uses the Input decorator to receive inputs from the parent component.  bgColor is a required input with an alias backgroundColor. name is an input that transforms the incoming value to uppercase.
export class SomeComponent {
 @Output() triple = new EventEmitter<number>();
 @Output('cube') powerXBy3 = new EventEmitter<number>();
 numSub = new BehaviorSubject<number>(2);
 @Output() double = this.numSub.pipe(map((n) => n * 2));
}
The same component also has three event emitters that apply the Output decorator. triple is an event emitter that emits a number. powerXBy3 is also a number event emitter with an alias cube. double consumes the value of the numSub behaviorSubject, multiples by 2, and emits the result to the parent component.  
Run the Angular signal schematics in a terminal to migrate the demo.
Migrate Input decorators to signal inputs
ng g @angular/core:signals
In the selection menu, select all the migrations and proceed to the next step.
After the signal input migration,
readonly bgColor = input.required<string>({ alias: "backgroundColor" }); 
readonly name = input<string, string>('input decorator', 
{ transform: (x: string) => x.toLocaleUpperCase() });
For bgColor, the input.required function is invoked with the alias option. For name,  the input is invoked with an initial value and the transform option to capitalize the input text. The migration has no impact on the parent component.
@Component({
 selector: 'app-some',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div>
     <p>bgColor: {{ bgColor() }}</p>
     <p>name: {{ name() }}</p>
 `,
})
export class SomeComponent {}
bgColor and name are signal inputs; the HTML template calls the signal functions to display their values.
After the output migration,
readonly triple = output<number>();
readonly powerXBy3 = output<number>({ alias: 'cube' });
numSub = new BehaviorSubject<number>(2); 
double = outputFromObservable(this.numSub.pipe(map((n) => n * 2)));
triple calls the output function that emits a number. powerX3 calls the output function and passes the alias option as the first argument. The value of alias is cube, same as before. double uses the outputFromObservable function to convert the Observable into an output. The migration also has no impact on the parent component.  
Convert the OutputRef to output
num = signal(2);
double = output<number>();
updateNum(value: number) {
   this.num.set(value);
   this.double.emit(value * 2);
} 
The numSub BehaviorSubject is converted to a signal, and the double OutputRef is converted to the output function. I defined a updateNum method to overwrite the num signal and emit that value to the double custom event.
<div>
    Num: <input type="number" [ngModel]="n" (ngModelChange)="updateNum($event)" />
</div>
Instead of emitting the value to the numSub Subject, the new value is passed to the updateNum method.
Example 2: Migrate the query decorators
export class QueriesComponent implements AfterContentInit {
 @ContentChild('header') header!: ElementRef<HTMLDivElement>;
 @ContentChildren('p') body!: QueryList<ElementRef<HTMLParagraphElement>>;
appendHeader = '';
list = '';
ngAfterContentInit(): void {
      this.appendHeader = `${this.header.nativeElement.textContent} Appended`;
      this.list = this.body.map((p) => p.nativeElement.textContent).join('---');
 }
}
The QueriesComponent component applies the ContentChild and ContentChildren decorators to query the HTML elements projected to the <ng-content> elements.
export class AppComponent implements AfterViewInit {
 @ViewChild(QueriesComponent) queries!: QueriesComponent;
 @ViewChildren('a') aComponents!: QueriesComponent[];
 viewChildName = '';
 numAComponents = 0;
 ngAfterViewInit(): void {
    this.viewChildName = this.queries.name; 
    this.numAComponents = this.aComponents.length;
 }
}
The AppComponent component uses the ViewChild decorator to query the first occurrence of the QueriesComponent.   It uses the ViewChildren decorator to query all the QueriesComponent components matching the template variable, a.
Migrate the query decorators to the query functions
After the query migration,
export class QueriesComponent implements AfterContentInit {
 readonly header = contentChild.required<ElementRef<HTMLDivElement>>('header');
 readonly body = contentChildren<ElementRef<HTMLParagraphElement>>('p');
 appendHeader = '';
 list = '';
 ngAfterContentInit(): void {
   this.appendHeader = `${this.header().nativeElement.textContent} Appended`;
   this.list = this.body().map((p) => p.nativeElement.textContent).join('---');
 }
}
<div>Appendheader: {{ appendHeader() }}</div>
<div>List: {{ list() }}</div>
The schematic migrates the ContentChild decorator to the contentChild function with the correct type. Similarly, the schematic migrates the ContentChildren decorator to the contentChildren function. The contentChild and contentChildren functions return a signal; therefore, the codes in the ngAfterContentInit lifecycle method are also modified. The method invokes the signal function before accessing the properties and assigning the results to the variables.
Convert instance members to computed signals
appendHeader = computed(() => `${this.header().nativeElement.textContent} Appended`);
list = computed(() => this.body().map((p) => p.nativeElement.textContent).join('---'));
The component removes the fterContentInit interface and the ngAfterContentInit method.  The appendHeader and list are converted to the computed signals.  The HTML template calls the functions to display the value of appenderHeader and list.
export class AppComponent implements AfterViewInit {
  readonly queries = viewChild.required(QueriesComponent);
  readonly aComponents = viewChildren('a');
  viewChildName = '';
  numAComponents = 0;
  ngAfterViewInit(): void {
    this.viewChildName = this.queries().name; 
    this.numAComponents = this.aComponents().length;
  }
}
The schematic migrates the ViewChild decorator to the viewChild function with the correct type. Similarly, the schematic migrates the ViewChildren decorator to the viewChildren function. The viewChild and viewChildren functions return a signal; therefore, the codes in the ngAfterViewInit lifecycle method are also modified. The method invokes the signal function before accessing the properties and assigning the results to the variables.
Convert instance members to computed signals
viewChildName = computed(() => this.queries().name);
numAComponents = computed(() => this.aComponents().length);
<p>ViewChildName: {{ viewChildName() }}</p>
<p>numAComponents: {{ numAComponents() }}</p>
The component removes the AfterViewInit interface and the ngAfterViewInit method.  The viewChildName and numAComponents are converted to the computed signals.  Finally, the HTML template calls the functions to display the values.
References:
- Signal migration: https://github.com/angular/angular/tree/main/packages/core/schematics/ng-generate/signals
- Decorators branch: https://github.com/railsstudent/ithome2024-demos/tree/refactor/day40-decorators/projects/day40-migration-schematic-demo
- Main branch: https://github.com/railsstudent/ithome2024-demos/tree/main/projects/day40-migration-schematic-demo
 

 
    
Top comments (0)