Angular 17.2 shipped, and with it came official confirmation and reasoning that the team intends to phase out RxJs as a mandatory dependency down the line.

Angular 17.2

With the release of Angular 17.2, components are nearing Signal Component status. The need for zone.js or an EventEmitter hasn't disappeared entirely, yet a substantial portion of the "Component API" is now Signal-compatible.

model()

The model() function yields a writable Signal that appears to the parent component as a two-way binding mechanism.

Before:

@Component({
  selector: 'app-tags',
  template: `<h3>Select Tags</h3>
    @for (tag of existingTags; track tag) {
      <button class="p-4 border-2 cursor-pointer" (click)="toggleTag(tag)">
        {{ tag }}
      </button>
    }`,
  standalone: true,
})
export class TagsComponent {
  existingTags = getTags();
  @Input() tags: string[] = [];
  @Output() tagsChange = new EventEmitter<string[]>();
  toggleTag(tag: string) {
    if (this.tags.includes(tag)) {
      this.tags = this.tags.filter((value) => value !== tag);
    } else {
      this.tags = [...this.tags, tag];
    }
    this.tagsChange.emit(this.tags);
  }
}
Enter fullscreen mode Exit fullscreen mode

Following that:

@Component({
  selector: 'app-tags',
  template: `<h3>Select Tags</h3>
    @for (tag of existingTags; track tag) {
      <button class="p-4 border-2 cursor-pointer" (click)="toggleTag(tag)">
        {{ tag }}
      </button>
    }`,
  standalone: true,
})
export class TagsComponent {
  existingTags = getTags();
  tags = model<string[]>([]);
  toggleTag(tag: string) {
    const tags = this.tags();
    if (tags.includes(tag)) {
      this.tags.update((tags) => tags.filter((value) => value !== tag));
    } else {
      this.tags.update((tags) => [...tags, tag]);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

"Banana Box" and Writable Signal

Beyond the new model function, the banana box syntax now extends its support to writable Signals:

Earlier:

@Component({
  selector: 'app-tags-selector',
  template: `
    <app-tags [(tags)]="tags"></app-tags>
    <p>Selected Tags: {{ prettyTags }}</p>
  `,
  standalone: true,
  imports: [TagsComponent, JsonPipe],
})
export class TagsSelectorComponent {
  tags: string[] = [];
  get prettyTags() {
    return this.tags.join(', '); // performance issue
  }
}
Enter fullscreen mode Exit fullscreen mode

Following that:

@Component({
  selector: 'app-tags-selector',
  template: `
    <app-tags [(tags)]="tags"></app-tags>
    <p>Selected Tags: {{ prettyTags() }}</p>
  `,
  standalone: true,
  imports: [TagsComponent, JsonPipe],
})
export class TagsSelectorComponent {
  tags = signal<string[]>([]);
  prettyTags = computed(() => this.tags().join(', '));
}
Enter fullscreen mode Exit fullscreen mode

Queries

Just as anticipated, signal-based counterparts for ViewChild and ContentChild are here too. These take the form of plain functions, requiring no decorator at all.

They remain in developer preview at the moment, yet once they reach production stability, they shift from being a mere option to becoming the standard approach for crafting state-of-the-art Angular applications.

The model and the query each offer a variant built around a required function—a concept first introduced alongside the Signal Input in 17.1.

Previously:

@Component({
  template: `
    <form>
      <input [(ngModel)]="user.firstname" name="firstname" />
      <input [(ngModel)]="user.lastname" name="lastname" />
    </form>
  `,
  standalone: true,
  imports: [FormsModule],
})
export class IntroductionComponent implements AfterViewInit {
  @ViewChild(NgForm) ngForm: NgForm | undefined;
  ngAfterViewInit(): void {
    if (!this.ngForm) {
      console.error('form is not available');
      return;
    }
    this.ngForm.form.valueChanges.subscribe(console.log);
  }
  user = { firstname: 'Konrad', lastname: 'Huber' };
}
Enter fullscreen mode Exit fullscreen mode

Following that:

@Component({
  template: `
    <form>
      <input [(ngModel)]="user.firstname" name="firstname" />
      <input [(ngModel)]="user.lastname" name="lastname" />
    </form>
  `,
  standalone: true,
  imports: [FormsModule],
})
export class IntroductionComponent {
  ngForm = viewChild.required(NgForm);
  constructor() {
    effect(() => this.ngForm().form.valueChanges.subscribe(console.log));
  }
  user = { firstname: 'Konrad', lastname: 'Huber' };
}
Enter fullscreen mode Exit fullscreen mode

With required, the undefined is stripped from the Signal's underlying type. This shifts the failure mode from something that could occur at compile time to an error thrown at runtime.

The onus is on us to avoid reading it prematurely. For queries, afterNextRender is the place to handle it.

As a rule, accessing the Signal in the template, inside a computed, or within an effect keeps us out of trouble.

The 17.2 release also brings support for Bun as an alternative to Node.js, along with NgOptimizedImage additions for loading images from Netlify. SSR support in DevTools received upgrades too. Material 3 is available experimentally.

Further Reading

Episode 24/07: Angular 17.2, optional RxJs — figure 1

Angular 17.2 novelties - Ninja Squad

The release of Angular 17.2 has arrived.

favicon blog.ninja-squad.com Episode 24/07: Angular 17.2, optional RxJs — figure 3

Signals for Component Interaction: Inputs, Bidirectional Bindings, plus Content and View Queries - ANGULARarchitects

favicon angulararchitects.io Episode 24/07: Angular 17.2, optional RxJs — figure 5

Angular 17.2 Release: Key Updates

This walkthrough covers everything introduced in Angular 17.2—from the signal-driven view queries (viewChild, viewChildren, contentChild, contentChildren) to the fresh model() two-way binding syntax.

favicon

Optional RxJs

Alongside the release of Angular 17.2, we now have the first concrete commitment from the Angular team that RxJs will become optional. This shift will necessitate significant modifications to the library's interface, which the team plans to roll out gradually across several major version updates.