A samurai card component receives its data from a parent: a name, a level, a clan. It used to take them through
@Input(), today it takes them through signals. This lesson covers three kinds of such inputs: a plain input() (an order travelling one way), model() (a conversation running both ways) and signal queries for child elements. One thing ties them all together - you read every one of them like any other signal, by calling ().input() turns data coming from the parent into a read-only signal. In the template you read it exactly like a normal signal: name().1import { Component, input } from '@angular/core';
2
3@Component({
4 selector: 'app-samurai-card',
5 template: `<h2>{{ name() }}</h2><p>Level: {{ level() }}</p>`
6})
7export class SamuraiCardComponent {
8 name = input('Unknown'); // optional, with a default value
9 level = input.required<number>(); // required
10 clan = input('Ronin', {
11 transform: (v: string) => v.toUpperCase() // transform it on the way in
12 });
13}Three variants cover most of what you will ever need.
input('Unknown') carries a default value for the case where the parent passes nothing. input.required<number>() does the opposite - Angular reports an error when the parent forgets to pass it, which protects you from a silent mistake. Look closely at the shape of that call: required is a property on input, not a separate function and not a decorator, so requiredInput<string>() and @RequiredInput() are names that do not exist, and @Input({ required: true }) is the old decorator this API replaces. transform runs the value through a function right at the entrance - here every clan is stored in upper case, with no manual processing inside the component. One more option is worth knowing, alias: written as samuraiId = input(0, { alias: 'id' }) it lets the parent bind [id] while the class keeps the longer name. The parent binds all of them as always: <app-samurai-card name="Musashi" [level]="10">.Sometimes the child should not only read a value but also change it - a health slider, for example.
model() gives you two-way binding: the parent supplies the value, the child updates it, and the parent sees the change.1import { Component, model } from '@angular/core';
2
3@Component({
4 selector: 'app-health-bar',
5 template: `
6 <input type="range" [value]="health()"
7 (input)="health.set(+$any($event.target).value)">
8 `
9})
10export class HealthBarComponent {
11 health = model(100); // works like [(ngModel)]
12}The difference from
input() is a single one, but it is fundamental: a model() can be set (health.set(...)), and the change travels back to the parent. That is why the parent binds it in both directions: <app-health-bar [(health)]="currentHealth">. Move the slider inside the child and currentHealth in the parent updates on its own. This is the signal-based heir of the old [(ngModel)], and it is the only one of these functions that supports the [(value)] syntax - input() is read-only, computed() derives a value you can never set, and output() merely pushes events out of the component through calls like selected.emit(name()), with nothing flowing back in.The last kind of input is not data from the parent but a handle on your own children in the template. The old
@ViewChild decorators now have signal-based counterparts.1import { viewChild, viewChildren, contentChild } from '@angular/core';
2
3export class ParentComponent {
4 childComponent = viewChild(ChildComponent); // one child from the template
5 allCards = viewChildren(CardComponent); // every matching child
6 projectedHeader = contentChild('header'); // a child passed in via <ng-content>
7}The names tell you where to look:
viewChild finds an element in the component's own template, while contentChild finds an element passed in from outside through content projection. The plural forms (viewChildren, contentChildren) return every match instead of the first. Notice the naming rule - each function simply repeats the name of its decorator in lower camel case, which is why querySignal(), childSignal() and selectChild() appear nowhere in Angular. A query can also reach a plain DOM element through its template reference, as in viewChild<ElementRef>('myInput'). The gain from the signal-based version is the same as everywhere else: the result is a signal, so you can build computed and effect on top of it instead of waiting for lifecycle hooks.Take one shared trait away from this lesson: all of these inputs are signals, and you read them by calling
(). input() receives an order from above, model() holds a conversation in both directions, and viewChild/contentChild hand you signal-based handles on your own children.