Signals

Angular 17.2 Is Out: What's New?

A complete guide to the brand new Angular 17.2 features, including: the new signal-based view queries viewChild, viewChildren, contentChild, and contentChildren, as well the new model() two-way binding mechanism.

Angular 17.2 Is Out: What's New? — Signals article by Angular University on Angular In Depth
Angular 17.2 Is Out: What's New? — Signals article by Angular University on Angular In Depth
On this page · 6 sections

The Angular 17.2 release brings a set of signal-based tools that simplify component development. This post covers the new viewChild, viewChildren, contentChild, and contentChildren functions, along with the recently introduced model() API for two-way data binding.

These signal-based query functions offer a streamlined alternative to the traditional decorators, potentially making lifecycle hooks like AfterContentInit and AfterViewInit redundant in many scenarios.

Let's examine these new features and see how they change the way we interact with the DOM and component state.

Article Roadmap

  • Signal-based Queries: viewChild and viewChildren
  • Signal-based Queries: contentChild and contentChildren
  • The model() API for Two-Way Binding
  • Key Takeaways

For a visual walkthrough of these features, you can refer to the following video from the Angular University YouTube channel:

Angular 17.2: SIGNAL COMPONENTS One Step Closer:

For further details, the official Angular Blog post on the Angular 17.2 release is also a good resource.

Exploring viewChild Signal-based Queries

First, we'll look at the new signal-based function that serves as an alternative to the @ViewChild decorator.

The established @ViewChild decorator is used to obtain a reference to a child component or element within a template:

@Component({
  selector: "signals-demo",
  template: `
    <p>Parent counter {{ parentCounter }}</p>

    <signal-counter [(count)]="parentCounter" />
  `,
  standalone: true,
  imports: [CounterComponent],
})
export class SignalsDemoComponent 
   implements AfterViewInit {
  parentCounter = 0;

  @ViewChild(SignalCounter)
  counter: SignalCounter;

  ngAfterViewInit() {
    console.log(
    "counter component:", this.counter);
  }
}

In this scenario, we have a SignalCounter component in our template and need a reference to it in our parent component class.

Previously, this required using the @ViewChild decorator:

  @ViewChild(SignalCounter)
  counter: SignalCounter;

With Angular 17.2, a signal-based approach is available.

We can now remove the @ViewChild decorator and the associated AfterViewInit lifecycle hook, which is no longer required for this purpose:

@Component({
  selector: "signals-demo",
  template: `
    <p>Parent counter 
      {{ parentCounter }}</p>

    <signal-counter #counter 
       [(count)]="parentCounter" />
  `,
  standalone: true,
  imports: [CounterComponent],
})
export class SignalsDemoComponent {

  parentCounter = 0;

  counter = viewChild(CounterComponent);

  constructor() {
    effect(() => {
      console.log("counter component:", 
         this.counter());
    });
  }
}

The functionality is the same, but the new API is noticeably more concise.

Instead of using the AfterViewInit hook, we can use a signal effect() to react to changes.

Notice that the resulting counter is now a Signal of type Signal<CounterComponent>.

This works just like any other Angular signal. You can use it with the computed function to derive new values or with effect() to trigger side effects when the value changes, as demonstrated above.

The signal-based query also supports the common options found in the ViewChild decorator.

You can pass an options object that includes properties like read:

counter = viewChild(CounterComponent, {
  read: true,
});

It's also possible to query by a template variable, such as the #counter reference:

counter = viewChild("counter");

This will yield the same component reference.

If the query must find a value, you can use the viewChild.required variant to enforce this:

counter = viewChild.required("counter");

The viewChildren Signal-based API

Let's also cover the viewChildren signal-based query.

When a component template contains multiple instances of the same child component, we can query all of them at once:

@Component({
  selector: "signals-demo",
  template: `
    <p>Parent counter {{ parentCounter }}</p>

    <signal-counter [(count)] />
    <signal-counter [(count)] />
    <signal-counter [(count)] />
  `,
  standalone: true,
  imports: [CounterComponent],
})
export class SignalsDemoComponent {
  parentCounter = 0;

  counters = viewChildren(CounterComponent);

  constructor() {
    effect(() => {
      console.log("counters component:", this.counters());
    });
  }
}

Again, no lifecycle hook is needed; using an effect() is sufficient to handle the results.

The contentChild and contentChildren Queries

New signal-based alternatives for the @ContentChild and @ContentChildren decorators are also introduced. They function in a manner analogous to the viewChild and viewChildren functions discussed above.

The model() API for Two-Way Binding

Now, let's turn our attention to the new model() API for signal-based two-way data binding.

Traditional two-way binding often uses the ngModel syntax, the "bananas in a box" [()] pattern. This is now achievable with signals using the model() function:

@Component({
  selector: "signal-counter",
  template: `
    <div>
      <div>Counter value: {{ count() }}</div>
      <button (click)="onIncrement()">Increment</button>
    </div>
  `,
  standalone: true,
})
export class CounterComponent {
  count = model(0);

  onIncrement() {
    this.count.update((val) => val + 1);
  }
}

Here, we treat the count property as a signal. Clicking the button increments its value.

Examining the type of count, we find it is a ModelSignal<number>.

This special type of WritableSignal enables two-way data binding from the parent component's perspective.

In the parent component's template:

@Component({
  selector: "signals-demo",
  template: `
    <p>Parent counter {{ parentCounter }}</p>

    <signal-counter [(count)]="parentCounter" />
  `,
  standalone: true,
  imports: [CounterComponent],
})
export class SignalsDemoComponent {
  parentCounter = 0;
}

We can use the standard [()] syntax to bind to the count property:

<signal-counter [(count)]="parentCounter" />

With this setup, the parentCounter variable stays automatically in sync with the count signal inside the SignalCounter component.

This binding works in both directions. If parentCounter is initialized to 100, the count value within SignalCounter will also start at 100.

Incrementing then proceeds from 100 to 101, then 102, and so on.

To stay informed about future posts and Angular ecosystem updates, consider subscribing to the newsletter.

For a comprehensive exploration of Angular Core features, including Signals, you can check out the Angular Core Deep Dive Course:

Angular 17.2 Is Out: What's New? — figure 1

Summary

These are the new APIs introduced in Angular 17.2, which simplify the process of building signal-based components.

Should you have any questions, please feel free to leave them in the comments. I'm happy to help.

AU
Angular University

Writes about RxJS, Components, Signals. Active 2015–2026.

All 79 articles →