Angular is a popular web development framework that has constantly evolved since its initial release. Every new version brings remarkable improvements and features that make development easier and more efficient. Angular is particularly well-suited for versatile web application development, encompassing dynamic applications, business-level applications with component reusability, as well as minimalist single-page or progressive web applications (SPA/PWA).
In May, the Angular team released version 16 of the framework with exciting new features and enhancements, such as signals, non-destructive SSR hydration, Vite integration and more. Being acquainted with these new features is crucial for Angular developers or those considering it for their upcoming projects, as understanding the latest advancements in Angular enables to harness the full potential of the framework and effectively leverage its capabilities.
In this article, we aim to delve into the significant innovations introduced by this latest iteration, shedding light on their potential to revolutionize the web application development landscape.
Introducing New Functionalities and Updates
Signals
Angular signals is a significant improvement in Angular v16 that allows developers to create reactive, real-time applications with less boilerplate code. Signals introduce a new way of handling asynchronous events, significantly reducing the need for repetitive subscription management. A signal is a distinctive form of variable that stores a value, but it goes beyond regular variables by offering notifications whenever its value changes. Angular v16 introduces a groundbreaking feature that allows signals to be utilized seamlessly across various components, directives, services, and even within templates and other parts of the application.
Simple signal example:
const hours = signal(11); // read signal console.log(‘current hours value – ‘ + hours()); // ‘current hours value – 11’ |
Also, we could optionally provide a generic type parameter to define the signal’s data type. A signal can encompass various data types, including strings, numbers, arrays, objects, and other desired data types.
const hours = signal<number>(11); const format = signal<string>(‘AM’); const someDate = signal<SomeDate>({ hours: 11, minutes: 20, day: ‘3’, month: ‘May’ }) |
Different ways of changing signal value
Signals in Angular offer an enhanced approach for code to communicate changes in data to templates and other code components. This advancement enhances Angular’s change detection mechanism, leading to improved performance and more reactive code.
Here are the ways to change the signal value:
- set() – replaces a signal with a new value
- update() – updates the signal based on its current value
- mutate() – method modifies the content of a signal value (modify array elements or object properties)
hours.set(12); hours.update(hrs => hrs + 2); someDate.mutate(dateValue => dateValue.hours = dateValue.hours + 2); |
As per the documentation’s rule, Angular’s change detection mechanism for a component is triggered exclusively when a signal accessed in the template notifies it has undergone a change.
Signal reactive primitives
Signal reactive primitives in Angular refer to the fundamental building blocks that facilitate the implementation of reactive patterns and enable efficient handling of state and data flow within Angular applications:
- computed – kind of signal that calculates its value from other signals;
- effect – function that execute when the value of signal it use is changed;
- signal – variable that holds value with possibility of change notification;
const hours = signal(11); const minutes = signal(20);const time = computed(() => `${ hours() } : ${ minutes() }`); const onTimeChange = effect(() => console.log(‘hours changed – ‘ + hours()))console.log(time()); // ’11 : 20’hours.set(12); // ‘hours changed – 12’ – side effect of ‘onTimeChange’ on hours signal value changeconsole.log(time()); // ’12 : 20′ |
The notable aspect about computed is that its value is memoized. This means if computed reads multiple times, the value is reused and not recalculated unless one of its dependent signals changes.
<div>Current hours value: {{ hours() }}</div> <div>Hours value: {{ hours() }}</div> <div>Show horse value: {{ hours() }}</div> |
Effect is useful when we need to run code after a signal changes, and that code has some side effects. For example, when we need to call some additional API, perform another operation (logging, external APIs).
Signals are particularly valuable when it comes to displaying data in the template that needs to react to other actions. By utilizing signals, developers can ensure that the displayed data in the template dynamically responds and updates based on user actions or changes in the application.
Non-Destructive SSR Hydration
Server-side rendering (SSR) has been an essential feature of Angular for optimizing web applications’ performance. Until version 16, Angular utilized destructive hydration, which involved destroying the existing Document Object Model (DOM) and creating a new one from scratch. However, starting from version 16, Angular introduced non-destructive server-side rendering (SSR) hydration. By adopting this approach, Angular endeavors to update only needed pieces of DOM without destroying the entire structure. This method ensures a smoother page rendering experience by eliminating flickering effects.
Vite for Development
Vite is a next-generation build tool that aims to enhance the development workflow of modern web projects, and it has been integrated into Angular version 16. Angular Vite provides an exceptional development experience, offering faster builds and improved Hot Module Replacement (HMR) performance. HMR allows for selective reloading of specific parts of your application when changes occur, eliminating the need to rebuild the entire app. The Vite integration also simplifies the configuration process, making it easier to start Angular development.
Currently, Vite is used only for the development server and is not turned on by default. It’s important to note that this integration is still considered experimental, indicating that it is undergoing active development and may not be fully stable or feature-complete.
… “architect”: { “build”: { /* Add the esbuild suffix */ “builder”: “@angular-devkit/build-angular:browser-esbuild”, … |
Required Input
The Angular community has eagerly awaited a highly anticipated feature: the ability to make Input parameters required. This feature, called “required component inputs,” gives developers the ability to mark specific inputs of a component as mandatory. If these inputs are not provided by the parent component, an error will be triggered. Previously there was no opportunity to make it required, and only some workarounds could help to achieve this result. From version 16, it is possible to enforce the presence of required inputs during development, reducing runtime errors by ensuring that all necessary inputs are provided when working with components.
@Input({ required: true }) name!: string; |
DestroyRef and takeUntilDestroyed
From version 16, Angular introduces a notable enhancement by making ngOnDestroy injectable. This means that developers can inject DestroyRef into their component, directive service, or pipe and use ngOnDestroy as well. This feature allows us to register callbacks for this lifecycle hook within an injection context, including outside the component. With DestroyRef, developers can encapsulate cleanup logic more straightforwardly and organized, leading to cleaner and more maintainable code.
And one more useful innovation is the takeUntilDestroyed RxJS operator. In the latest version of Angular, if we want to tie the lifecycle of some observable, we need to add it to the pipe. By utilizing the takeUntilDestroyed operator, developers can maintain cleaner code and more effectively manage observables, contributing to the overall stability and performance of the applications.
@Component(…) class SomeComponent { destroyRef = inject(DestroyRef); ngOnInit() { this.service.getData() .pipe(takeUntilDestroyed()) .subscribe(response => /* some action */) } destroy() { this.destroyRef.onDestroy(() => /* some action */ ); } } |
Route parameters binding
In Angular 16, it is not necessary anymore to inject ActivatedRoute to get route parameters. Instead, this can be done by binding route parameters to the corresponding component’s inputs. This functionality encompasses various types of parameters, including router data, path parameters, and query parameters.
const routes = [ { path: ‘time’, loadComponent: import(‘./time’), resolve: { time: () => getTime() } } ];@Component(…) export class Time { // The value of “time” is passed to the contact input @Input() time?: string; } |
Conclusion
Angular v16 is a significant step forward within the Angular ecosystem, offering developers many new features and improvements. Angular consistently strengthens its capabilities each year, ensuring a seamless development experience that further solidifies its position as a powerful, reliable, and efficient web application framework. The collaborative efforts between the framework’s development team and the community have resulted in the implementation of highly requested features. By incorporating these highly anticipated features, such as signals and server-side rendering (SSR), Angular aligns itself with other modern frameworks, ensuring it remains competitive in the evolving landscape of web development. Some of them are still experimental, but no doubt they will revolutionize the application development process, making them clearer and more optimized.