Episode 24/07: Angular 17.2, optional RxJs
Tags: webdev, javascript, programming, angular
Published: 2024-02-19 19:19:47

We got Angular 17.2 and a written confirmation along with an explanation that the Angular team wants to make RxJs optional in the upcoming years.

{% embed https://youtu.be/4-B51R-E5So %}

## Angular 17.2

Angular 17.2 is out. Due to new features, a component is now almost a  Signal Component. We still require zone.js or an EventEmitter, but quite a lot of the "Component API' works now with a Signal.

### model()

The model function creates a writable Signal, presenting itself to the parent component as two-way binding.

Before:

```typescript
@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);
  }
}
```

After:

```typescript
@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]);
    }
  }
}
```

### "Banana Box" and Writable Signal

Separated from the new model function, the banana box syntax now supports also writable Signals:

Before:

```typescript
@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
  }
}
```

After:

```typescript
@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(', '));
}
```

### Queries

As planned, we also have signal-based alternatives for ViewChild and ContentChild. They are also simple functions and go without a decorator. 

These features are still in developer preview, but as soon as they go production-ready, they're no longer an alternative but the preferred way to write modern Angular applications.

The model and the query come in a variation with a required function. The required function is something we already saw with the Signal Input in 17.1.

Before:

```typescript
@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' };
}
```

After:

```typescript
@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' };
}
```

Required removes the undefined from the Signal's underlying type. We are switching from a potential compilation to a runtime error.

It is our responsibility that we don't directly access it too early. For the queries, we can use afterNextRender.

In general, we are safe if we access the Signal in the template or via a computed or effect.

Other features in 17.2 are the support for Bun, an alternative to Node.js, and the NgOptimizedImage, which supports loading images from Netlify. And the DevTools also received improvements for SSR. We have experimental support for Material 3.


### Further Reading

{% embed https://blog.angular.io/angular-v17-2-is-now-available-596cbe96242d %}

{% embed https://blog.angular.io/material-3-experimental-support-in-angular-17-2-8e681dde650e %}

{% embed https://blog.ninja-squad.com/2024/02/14/what-is-new-angular-17.2/ %}

{% embed https://www.angulararchitects.io/en/blog/component-communication-with-signals-inputs-two-way-bindings-and-content-view-queries/ %}

{% embed https://blog.angular-university.io/angular-17-2-release/ %}

## Optional RxJs

Next to Angular 17.2, we have, for the first time, written confirmation that the Angular team will make RxJs optional. That will require huge changes to the API, which will gradually happen over multiple major releases.

{% embed https://twitter.com/mgechev/status/1757164399771717786 %}