Introduction
Wednesday May 22, 2024, the Angular core team releases a new version of Angular: version 18.
This version not only stabilizes the latest APIs, but also introduces a number of new features designed to simplify use of the framework and improve the developer experience.
What are these new features? Read on to find out.
New Control flow syntax is now stable
When the latest version of Angular was released, a new way of managing the flow of a view was introduced. As a reminder, this new control flow is directly integrated into the Angular template compiler, making the following structural directives optional:
- ngIf
- ngFor
- ngSwitch / ngSwitchCase
<!-- old way -->
<div *ngIf="user">{{ user.name }}</div>
<!-- new way -->
@if(user) {
<div>{{ user.name }}</div>
}
This new API is now stable, and we recommend using this new syntax.
If you'd like to migrate your application to this new control flow, a schematics is available.
ng g @angular/core:control-flow
Also, the new @for syntax, which replaces the ngFor directive, obliges us to use the track option to optimize the rendering of our lists and avoid their total recreation during a change.
Two new warnings have been added in development mode:
a warning if the tracking key is duplicated. This warning is raised if the chosen key value is not unique in your collection.
a warning if the tracking key is the entire item and the choice of this key results in the destruction and recreation of the entire list. This warning will appear if the operation is considered too costly (but the bar is low).
Defer syntax is now stable
The @defer syntax, also introduced in the latest version of Angular, lets you define a block to be lazyloaded when a condition is met. Of course, any third-party directive, pipe or library used in this block will also be lazyloaded.
Here's an example of its use
@defer(when user.name === 'Angular') {
<app-angular-details />
}@placeholder {
<div>displayed until user.name is not equal to Angular</div>
}@loading(after: 100ms; minimum 1s) {
<app-loader />
}@error {
<app-error />
}
As a reminder,
- the @placeholder block will be displayed as long as the @defer block condition is not met
- the @loading block will be displayed when the browser downloads the content of the @defer block; in our case, the block loading will be displayed if the download takes more than 100ms, and will be displayed for a minimum duration of 1 second.
- the @error block will be displayed if an error occurs while downloading the @defer block
What will happen to Zone js
Angular 18 introduces a new way of triggering a detection change. Previously, and not surprisingly, the detection change was handled entirely by Zone Js. Now, the detection change is triggered directly by the framework itself.
To make this feasible, a new change detection scheduler has been added to the framework (ChangeDetectionScheduler) and this scheduler will be used internally to raise a change detection. This new scheduler is no longer based on Zone Js and is used by default with Angular version 18.
This new scheduler will raise a detection change if
- a template or host listener event is triggered
- a view is attached or deleted
- an async pipe receives a new value
- the markForCheck function is called
- the value of a signal changes etc.
Small culture moment: this detection change is due to the call to the ApplicationRef.tick function internally.
As I mentioned above, since version 18 Angular has been based on this new scheduler, so when you migrate your application, nothing should break in the sense that Angular will potentially be notified of a detection change by Zone Js and/or this new scheduler.
However, to return to the pre-Angular 18 behavior, you can use the provideZoneChangeDetection function with the ignoreChangesOutsideZone setter option set to true.
bootstrapApplication(AppComponent, {
providers: [
provideZoneChangeDetection({ ignoreChangesOutsideZone: true })
]
});
Also, if you wish to rely only on the new scheduler without depending on Zone Js, you can use the provideExperimentalZonelessChangeDetection function.
bootstrapApplication(AppComponent, {
providers: [
provideExperimentalZonelessChangeDetection()
]
});
By implementing the provideExperimentalZonelessChangeDetection function, Angular is no longer dependent on Zone Js, which makes it possible to
- remove the Zone js dependency if none of the project's other dependencies depend on it
- remove zone js from polifills in angular.json file
Deprecation of HttpClientModule
Since version 14 of Angular and the arrival of standalone components, modules have become optional in Angular, and now it's time to see the first module deprecated: I've named the HttpClientModule
This module was in charge of registering the HttpClient singleton for your entire application, as well as registering interceptors.
This module can easily be replaced by the provideHttpClient function, with options to support XSRF and JSONP.
This function has a twin sister for testing: provideHttpClientTesting
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient()
]
});
As usual, the Angular team has provided schematics to help you migrate your application.
When issuing the ng update @angular/core @angular /cli command, a request will be made to migrate the HttpClientModule if used in the application
ng-content fallback
ng-content is an important feature in Angular, especially when designing generic components.
This tag allows you to project your own content. However, this feature had one major flaw. You couldn't give it a default content.
Since version 18, this is no longer the case. You can have content inside the tag that will be displayed if no content is provided by the developer.
Let's take a button component as an example
<button>
<ng-content select=".icon">
<i aria-hidden="true" class="material-icons">send</i>
</ng-content>
<ng-content></ng-content>
</button>
The icon send will be displayed if no element with the icon class is provided when using the button component
Form Events: a way to group the event of the form
It's a request that was made by the community a long time ago: to have an api to group together the events that can happen in a form; and when I say events, I mean the following events
- pristine
- touched
- status change
- reset
- submit
Version 18 of Angular exposes a new event property from the AbstractControl class (allowing this property to be inherited by FormControl, FormGroup and FormArray), which returns an observable
@Component()
export class AppComponent {
login = new FormControl<string | null>(null);
constructor() {
this.login.events.subscribe(event => {
if (event instanceof TouchedChangeEvent) {
console.log(event.touched);
} else if (event instanceof PristineChangeEvent) {
console.log(event.pristine);
} else if (event instanceof StatusChangeEvent) {
console.log(event.status);
} else if (event instanceof ValueChangeEvent) {
console.log(event.value);
} else if (event instanceof FormResetEvent) {
console.log('Reset');
} else if (event instanceof FormSubmitEvent) {
console.log('Submit');
}
})
}
}
Routing: redirect as a function
Before the latest version of Angular, when you wanted to redirect to another path, you used the redirectTo property. This property took as its value only a character string
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMath: 'full' },
{ path: 'home', component: HomeComponent }
];
It is now possible to pass a function with this property. This function takes ActivatedRouteSnapshot as a parameter, allowing you to retrieve queryParams or params from the url.
Another interesting point is that this function is called in an injection context, making it possible to inject services.
const routes: Routes = [
{ path: '', redirectTo: (data: ActivatedRouteSnapshot) => {
const queryParams = data.queryParams
if(querParams.get('mode') === 'legacy') {
const urlTree = router.parseUrl('/home-legacy');
urlTree.queryParams = queryParams;
return urlTree;
}
return '/home';
}, pathMath: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'home-legacy', component: HomeLegacyComponent }
];
Server Side Rendering: two new awesome feature
Angular 18 introduces two important and much-awaited new server-side rendering features
- event replay
- internationalization
Replay events
When we create a server-side rendering application, the application is sent back to the browser in html format, displaying a static page that then becomes dynamic thanks to the hydration phenomenon. During this hydration phase, no response to an interaction can be sent, so user interaction is lost until hydration is complete.
Angular is able to record user interactions during this hydration phase and replay them once the application is fully loaded and interactive.
To unlock this feature, still in developer preview, you can use the ServerSideFeature withReplayEvents function.
providers: [
provideClientHydration(withReplayEvents())
]
Internationalization
With the release of Angular 16, Angular has changed the way it hydrates a page. Destructive hydration has given way to progressive hydration. However, an important feature was missing at the time: internationalization support. Angular skipped the elements marked i18n.
With this new version, this is no longer the case. Please note that this feature is still in development preview and can be activated using the withI18nSupport function.
providers: [
provideClientHydration(withI18nSupport())
]
Internationalization
Angular recommends using the INTL native javascript API for all matters concerning the internationalization of an Angular application.
With this recommendation, the function helpers exposed by the @angular/common package have become deprecated. As a result, functions such as getLocaleDateFormat, for example, are no longer recommended.
A new builder package and deprecation
Up until now, and since the arrival of vite in Angular, the builder used to build the Angular application was in the package: @angular-devkit/build-angular
This package contained Vite, Webpack and Esbuild. A package that's far too heavy for applications that in future will use only Vite and Esbuild.
With this potential future in mind, a new package containing only Vite and Esbuild was created under the name @angular/build
When migrating to Angular 18, an optional schematic can be run if the application does not rely on webpack (e.g. no Karma-based unit testing). This schematic will modify the angular.json file to use the new package and update the package.json by adding the new package and deleting the old one.
Importantly, the old package can continue to be used, as it provides an alias to the new package.
Angular supports Less Sass Css and PostCss out of the box, by adding the necessary dependencies in your project's node_modules.
However, with the arrival of the new @angular/build package, Less and PostCss become optional and must be explicit in the package.json as dev dependencies.
When you migrate to Angular 18, these dependencies will be added automatically if you wish to use the new package.
No more donwleveling async/await
Zone js does not work with the Javascript feature async/await.
In order not to restrict the developer from using this feature, Angular's CLI transforms code using async/await into a "regular" Promise.
This transformation is called downleveling, just as it transforms Es2017 code into Es2015 code.
With the arrival of applications no longer based on Zone Js, even if this remains experimental for the moment, Angular will no longer downlevel if ZoneJs is no longer declared in polyfills.
The application build will therefore be a little faster and a little lighter.
An new alias: ng dev
From now on, when the ng dev command is run, the application will be launched in development mode.
In reality, the ng dev command is an alias for the ng serve command.
This alias has been created to align with the Vite ecosystem, particularly the npm run dev command.
Future
Once again, the Angular team has delivered a version full of new features that will undoubtedly greatly enhance the developer experience and show us that Angular's future looks bright.
What can we expect in the future?
Undoubtedly continued improvements in performance and developer experience.
We'll also see the introduction of signal-based forms, signal-based components and, very soon, the ability to declare template variables using the @let block.
Top comments (8)
Great !!
Good to see the Angular renaissance is in full swing.
Hi Nicolas Frizzarin,
Top, very nice !
Thanks for sharing
What about ng deep. Are we not getting any thing as replacement? as it deprecated. It was very nice to use it and change the style.
It's been deprecated since around Angular 6. I'm still using it today since there's no practical alternative.
Instead of using ::ng-deep to style components from component libraries, I recommend editing the styles directly in the styles.scss file. The styles defined in the styles.scss file, or any SCSS file that's imported into the styles.scss file, are applied globally, meaning they can affect any element inside third-party component libraries. Editing the styles.scss file also allows you to have a single point of truth for your styles, making it easier to manage and maintain your styles without having ::ng-deep scattered throughout your component styles.
Thanks for this nice article. One question: if I customized webpack for build, since Angular will not be using Webpack anymore, I guess there is a way to customize esbuild the same way?
Still, you want to know more features, here it is: Angular 18 Features