BLOG
18 January 2023

What's new in Angular 15? Let's take a look

tech

Angular is one of the most popular front-end frameworks. It allows you to create single-page applications using TypeScript. The latest version (v15) was officially released a few days ago and introduced considerable changes, descriptions of which you can find below. 

Standalone components   

In the previous version (v14) standalone components APIs to build applications without using NgModules were introduced. From now on, these APIs moved from developer preview and are now part of the stable API surface. Also, they will be evolving gradually following the semantic versioning. 

Standalone components work in Angular, and now they also are fully operational in HttpClient, Angular Elements, router and more. These APIs allow you to bootstrap an application using a single component: 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import {bootstrapApplication} from '@angular/platform-browser'; import {ImageGridComponent} from'./image-grid'; @Component({ standalone: true, selector: 'photo-gallery', imports: [ImageGridComponent], template: ` … <image-grid [images]="imageList"></image-grid> `, }) export class PhotoGalleryComponent { // component logic } bootstrapApplication(PhotoGalleryComponent);

Multi-Route Application   

In the new version you can build a multi-route application using the new router standalone APIs. Here is an example of how to do achieve that: 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export const appRoutes: Routes = [{ path: 'lazy', loadChildren: () => import('./lazy/lazy.routes') .then(routes => routes.lazyRoutes) }]; Next, declare the lazyRoutes in this way import {Routes} from '@angular/router'; import {LazyComponent} from './lazy.component'; export const lazyRoutes: Routes = [{path: '', component: LazyComponent}]; And in the end, register appRoutes in the bootstrapApplication call: bootstrapApplication(AppComponent, { providers: [ provideRouter(appRoutes) ] });

Another benefit of the provideRouter API is that it’s tree-shakeable! Bundlers can remove unused features of the router at build-time. This allows to decrease the size of the router code in the application bundle. 

Router unwraps default imports 

Router now auto-unwraps default export when lazy loading, to make router simpler and reduce the amount of code. 

Now we can add the route: 

1 2 3 4 { path: 'lazy', loadComponent: () => import('./lazy-file'), }

And thus router will look for a default export and use it automatically 

Functional router guards 

While working on standalone router APIs, boilerplate in guards have also been reduced. To implement the guard, which will verify if a user is logged in, you can use: 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Injectable({ providedIn: 'root' }) export class MyGuardWithDependency implements CanActivate { constructor(private loginService: LoginService) {} canActivate() { return this.loginService.isLoggedIn(); } } const route = { path: 'somePath', canActivate: [MyGuardWithDependency] }; The guard invokes isLoggedIn() only, while the all logic is inside the service, so the guard itself is simple. We have some code that isn't needed there, though. With the new functional router guards, you can put this code above instead. const route = { path: 'admin', canActivate: [() => inject(LoginService).isLoggedIn()] };

In this part of the code, we implement the guard directly in the declaration, minimizing the code as a result. 

Directive Composition API 

Due to the large number of requests on GitHub, directives have been added to the host element - it's a new level of code reusability. The directive composition API brings up code usability, and it's made possible thanks to their compiler only. 

The directive composition API only works with standalone directives. 

1 2 3 4 5 6 7 8 9 @Component({ selector: 'mat-menu', hostDirectives: [HasColor, { directive: CdkMenu, inputs: ['cdkMenuDisabled: disabled'], outputs: ['cdkMenuClosed: closed'] }] }) class MatMenu {}

In the above example we extend MatMenu with two directives: HasColor and CdkMenu. For the first directive reuses all inputs and outputs and related logic, while for the CdkMenu uses only logic and specified inputs 

This feature can remind you of multiple inheritance known from other languages. The difference is the mechanism for the resolution of name conflicts, and the fact that it is now applicable to user interface primitives. 

Image directive   

In current version of Angular image, the directive is stable - first time introduced in cooperation with Chrome Aurora in 14.2 as developer preview. Some features for the image directives were also added in this version. 

Automatic srcset generation: the directive ensures that an appropriately sized image is requested by generating the srcset attribute for you. This can reduce download times for your images. 

Fill mode - it's experimental, causes the image to fill its parent container, without the need to specify the image width and height. It's useful if you don’t know the sizes of your images or if you’d like to migrate CSS background images to use the directive. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 NgOptimizedImage can be used directly in your component or NgModule: import { NgOptimizedImage } from '@angular/common'; // Include it into the necessary NgModule @NgModule({ imports: [NgOptimizedImage], }) class AppModule {} // ... or a standalone Component @Component({ standalone: true imports: [NgOptimizedImage], }) class MyStandaloneComponent {}

To use in the component, you now have to add ngSrc instead of src and specify the priority attribute to optimize the speed from the LCP images.

Better stack traces  

Based on developers' feedback, Angular decided to improve error massages. Therefore, they have started cooperation with Chrome DevTools to resolve the issue. In the new version the Angular application debugging process has been simplified. 

Before those changes, one line with the error message was related to developer code and the other came from related libraries. There was also no information provided about what interaction caused the error. 

Now errors from node_modules are ignored, thanks to the addition of source maps by Angular CLI. They also worked on async stack tagging API to allow combining  independent, scheduled async tasks into one stack trace. 

This is an example of new error messages. Here you can track all the way from click button to error. 

ERROR Error: Uncaught (in promise): Error 
Error 
at app.component.ts:18:11 
at fetch (async)  
at (anonymous) (app.component.ts:4) 
at request (app.component.ts:4) 
at (anonymous) (app.component.ts:17) 
at submit (app.component.ts:15) 
at AppComponent_click_3_listener (app.component.html:4) 

The release of MDC-based components to stable  

Refactoring material component based on Material Desing Components has been done. Angular team worked on adjusting own component and improved the accessibility and compatibility of Angular Material Design Specifications. This makes it possible to use Material 3. 

Lots of changes were made in DOM and CSS, and consequently some styles should be adjusted, especially individual styles that override the internal elements of the migrated components. 

All previous implementations of material component have been cut out. If you want to still use current components, you should change the import to legacy e.g.: 

import {MatLegacyButtonModule} from '@angular/material/legacy-button';

More improvements in components  

The range selection issue has been fixed, and additionally all components have an API to customize density. New version of components has a lot of accessibility improvements e.g., better contrast, increased touch area and better ARIA semantics. 

CDK Listbox  

The Component Dev Kit offers a set of behavior primitives for building UI components. In Angular 15, a new primitive named CDK Listbox appeared.  

The @angular/cdk/listbox module helps developers to create custom listboxes with interactions based on the WAI ARIA listbox pattern. This component supports interactions with keyboard, bidi layout, and focus management to ensure accessible experience. All of directives apply related ARIA roles to their host element 

Improvements in the experimental esbuild support  

Angular 14 came with  experimental support for esbuild in Ng build to enable faster build times and simplify pipeline. 

In Angular 15, we have experimental support for the ng build --watch, Sass, SVG template and file replacement.  

To use it, update your builder in angular.json to: 

"builder": "@angular-devkit/build-angular:browser-esbuild"

Automatic imports in language service  

Now you can automatically import components that you are using in a template that weren't added to component or NgModule. 

CLI improvements 

In the Angular CLI support for standalone components has been introduced. Now you can generate a new one with the command below: 

ng g component –standalone

You can define the target ECMAScript version in .browserlist, to reduce the configuration overhead. 

The configuration was also reduced by removing test.ts, polyfills.ts and some environments. Now you can specify that in angular.json 

Summary 

With both previous changes and the removal of Angular legacy compiler, this update makes a lot of adjustments to extend stability and supportability, which improves developer efficiency. 

Launching Ivy in 2020 was the best decision, which allowed to implement lot of improvements e.g., NgModules. These makes pick up Angular is simpler and easier. 

The angular team is working on improvements in the server-side rendering pipeline and reactivity while bringing high quality improvements everywhere. 

So don't wait - update your angular application to the latest version and have fun coding! 



Author
Łukasz Zaroda
Software Developer

I'm a full-stack web developer with strong experience with ASP.NET and Javascript. I have been working in IT since 2016, currently as full-stack developer in Softwarehut. I'm interested in technology and MMO games and when the weather isn't too bad, riding a motorcycle.