What's new in Angular 18.2?

Angular 18.2.0 is here!

Angular logo

This is a minor release with some nice features: let’s dive in!

Automatic flush in fakeAsync

In Angular v18.2 (and zone.js v0.15), the flush() function is now automatically called at the end of a fakeAsync() test.

Before this version, you had to call flush() yourself at the end of your test to flush all pending asynchronous tasks or discardPeriodicTasks() for periodic tasks. If you didn’t do it, you would get the error 1 periodic timer(s) still in the queue.

The way to fix it was to call flush() or discardPeriodicTasks() at the end of your test:

it('should do something', fakeAsync(() => {
  // ...
  flush();
}));

This is no longer necessary, as Angular will do it for you (if you manually set the flush option to true with Angular v18.2/zone.js v0.14, and will be done by default by Angular in v19/zone.js v0.15).

it('should do something', fakeAsync(() => {
  // ...
  // no flush() or discardPeriodicTasks() required!
}, { flush: true }));

When zone.js v0.15 is released, you won’t need to specify the flush option anymore, as it will be done automatically for you:

it('should do something', fakeAsync(() => {
  // ...
  flush();
}));

whenStable helper

A new helper method has been added to ApplicationRef to wait for the application to become stable. whenStable is really similar to the existing isStable method, but it returns a promise that resolves when the application is stable instead of an observable.

defaultQueryParamsHandling in router

It is now possible to specify the default query params handling strategy for all routes in the provideRouter() configuration.

provideRouter(routes, withRouterConfig({ defaultQueryParamsHandling: 'merge' }));

By default, Angular uses the replace strategy, but you can also use preserve or merge. Previously, you could only specify this strategy on a per-navigation basis (via RouterLink or router.navigate options).

Migrations

An optional migration has been added to migrate dependency injection done via the constructor to the inject function.

ng g @angular/core:inject

This will update your code from:

export class UserComponent {
  constructor(private userService: UserService) {}
}

to:

export class UserComponent {
  private userService = inject(UserService);
}

Note that you might have some compilation errors after this migration, most notably if you were using new UserComponent(userService) in your tests. There are a few options for this migration, and one can mitigate this issue:

  • path, the directory to run the migration. By default: .;
  • migrateAbstractClasses, whether to migrate the abstract classes or not (which may break your code and necessitate to manually fixing it). By default: false;
  • backwardsCompatibleConstructors: by default, constructors that are empty after the migration are deleted. This can lead to compilation errors like the one I’m mentioning above. To prevent that, you can generate backward-compatible constructors with this option (which looks like: constructor(...args: unknown[]);). By default: false;
  • nonNullableOptional: whether to cast the optional inject sites to be non-nullable or not. By default: false.

The migration is optional and the Angular team explicitly said that the constructor injection will still be supported in the future. However, it does indicate that the future of Angular might be to use the inject function.

Another optional migration has been added to convert standalone components used in routes to be lazy-loaded if that’s not the case:

ng g @angular/core:route-lazy-loading

This will update your code from:

{
  path: 'users',
  component: UsersComponent
}

to:

{
  path: 'users',
  loadComponent: () => import('./users/users.component').then(m => m.UsersComponent)
}

The only option for this migration is the path.

Diagnostics

A new diagnostic has been added to catch uncalled functions in event bindings:

<button (click)="login">Log in</button>

throws: NG8111: Function in event binding should be invoked: login().

Another one was added to catch unused @let declaration (a new feature introduced in Angular 18.1): NG8112: @let user is declared but its value is never read.

Angular CLI

The application builder now supports attribute-based loader configuration

For example, an SVG file can be imported as text via:

// @ts-expect-error TypeScript cannot provide types based on attributes yet
import contents from './some-file.svg' with { loader: 'text' };

This overrides all other configurations (for example a loader defined in angular.json).

Summary

That’s all for this release, stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!



blog comments powered by Disqus