Angular 17 - What's new?

Not just a rebranding!

Angular 17 - What's new?

posted in javascript on

Angular 17 has landed with a rebranding, and an a new documentation home which now also offers an interactive learning experience, but what else is new?

There are a plethora of improvements such as:

  • hydration graduated from developer preview
  • standalone APIs is now the default
  • new @angular/ssr package
  • improved SSR functionality
  • the Signals API is mostly out of developer preview (effect() remains in it)
  • new afterRender and afterNextRender lifecycle hooks
  • Vite & esbuild are now the default for new projects
  • experimental view transition support
  • automatic preconnect in the image directive
  • defer loading of the animations module
  • style and styleUrls as strings

and so much more as you can see in the release notes.
But I’d like to dive into some that offer quite the Quality of Life improvements

Built-in control flow

Before Angular 17 we’ve always used *ngIf, *ngSwitch, and *ngFor, but now there’s a new block template syntax in preview which feels a lot more intuitive, requires no special imports and has better type checking thanks to its improved type narrowing.

You can automatically migrate existing project with ng generate @angular/core:control-flow

If/else and even else if!

Traditionally we’d do

<div *ngIf="usingLatest; else outdated">
   You’re using the latest version. 
</div> 
<ng-template #outdated>
   You are using an outdated version 
</ng-template>

Now we can do

@if (usingLatest) {
  Thank you for using the latest version
} @else {
  You are using an outdated version
}

This construction also allows us to write an if/else if/else construction which formerly wasn’t possible.

Iteration

To loop over a list of elements we’d perform

<div *ngFor="let angularRelease of angularReleases">
  Release: {{ angularRelease.versionNumber }} - Date: {{ angularRelease.date }} <br>
</div>

With optionally a trackBy function. In the new approach, track is mandatory. The new for statement uses a new diffing algorithm which is quite a bit faster according to community framework benchmarks.
It’s also quite easy to add an empty placeholder.

@for (angularRelease of angularReleases; track angularRelease.versionNumber) {
  Release: {{ angularRelease.versionNumber }} - Date: {{ angularRelease.date }} <br>
} @empty {
  Empty list of releases
}

Switches

Switches have also become quite a lot more convenient, from:

<div [ngSwitch]="roll">
  <app-one *ngSwitchCase="1"/>
  <app-two *ngSwitchCase="2"/>
  <app-three *ngSwitchDefault/>
</div>

To

@switch (roll) {
  @case (1) { <app-one/> }
  @case (2) { <app-two/> }
  @default { <app-three/> }
}

Deferrable views

This is the planned roads forwards for lazy loading. It utilizes the new block mechanism for standalone components to make our applications faster, by as the name implies easily facilitating declarative, deferred loading. One of the nice things about this, is that this all also happens at compile-time, all the complexity is abstracted away.

Let’s use this example:

@defer (on hover) {
  <app-heavy/>
} @loading {
  Loading…
} @error {
  Loading failed
} @placeholder (minimum 2000ms) {
  <div>Placeholder that will be shown for 2000ms minimally (to prevent flickering in case the deferred component loads quickly)</div>
}

In this example, we’re deferring the loading of a heavy component, until the block’s been hovered over. In the interim we’ll be showcasing the placeholder (and in this case, minimally for 2 seconds). After which we’ll see either the loading in case it takes long/the result/loading failed in case of an error.

Only @defer is required, @loading | @error | @placeholder are all optional.

There possible triggers are:

  • on idle => there default, triggers loading once the browser has reached an idle state
  • on viewport => when the specified content enters the viewport
  • on hover => when the user hovers over the trigger area
  • on interact => when the user interacts with the specified element
  • on immediate => triggered once the client has finished rendering
  • on timer => delays the loading with a timer

Another nice thing is that deferrable views also allow us the option to prefetch dependencies before rendering them. This can be done by adding a prefetch to the defer block (the same triggers as above are supported).

@defer (on viewport; prefetch on idle) {...}


Other interesting reads
Tags: angular