Catching Up with Angular’s Latest Changes

Key highlights of Angular’s latest version releases.

The latest versions of Angular introduced many changes to the framework.

Some call it a renaissance. Others call it an attempt to simplify the framework for newbie developers and to reach a broader audience.

Regardless of how you call it, one thing is for sure — there’s a lot going on lately in Angular!

In this article, we will go through the most important changes that happened in the latest versions of Angular (v14 — v17 specifically).

Some of them are major, while others are minor. But each one of them comes to add to the new picture of the framework.

Let’s get started!

Enhanced inject function

Starting from Angular v14, we can use the inject() function outside an injection context.

As you can imagine, this opens up many possibilities. Here is an example:

Example of using the inject() function.

However, there is one limitation — the devil is in the details.

We can use inject() outside an injection context, but we can only call it inside one (like say, a constructor).

Limitations of the inject() function.

The use inside ngOnInit will throw an error during runtime.

Inject function runtime error.

Oh, and one more thing: it usually makes testing harder!

That being said, use it at your discretion.

Standalone components

Angular v14 is also the version where standalone components made an entrance.

Basically, NgModule became optional.

Creating standalone components, directives, or pipes is as simple as adding standalone: true to the metadata of their decorator.

Creating an Angular standalone component.

Using standalone components comes with several benefits, such as more tree-shakable routing and more fine-grained lazy loading.

That of course doesn’t mean one has to migrate everything to standalone. If it doesn’t make sense for a case, then don’t!

Functional guards, resolvers, and interceptors

Angular v14 introduced functional route guards.

Angular functional guards example snippet.

Before this change, we needed to create a service class, implement the respective interface, and then of course implement its method(s).

Class-based Angular guards example snippet

This worked fine but required a lot of extra code (see 10 vs. 3 lines of code).

Functional guards are then used the same way as class-based guards.

Provide Angular functional guard example snippet

After the success of functional guards, functional interceptors and resolvers were next. These were introduced in Angular v15.

Demo snippet of functional interceptors in Angular

By the way, the class-based implementations were deprecated in favor of the functional ones.

Required component inputs

Starting from Angular v16, we can declare inputs as required for components and directives.

Required input parameters demo snippet

This will give a compilation error if we don’t provide the required input.

Required input error

Routing parameters to routed component inputs

Angular v16 has introduced another input-related feature, which enables the automatic binding of router information to a routed component’s inputs.

That information can be query parameters, required (or path) parameters, static data, and resolver data.

Conflicts are resolved with a predefined priority:

  1. data (static or resolved)

  2. required parameters

  3. query parameters

To enable this feature, add withComponentInputBinding() in the provideRouter().

Enabling routed component input binding

Now, assuming that the URL ends with products/123?searchTerm=Laptop, we can access directly this information in a component as component inputs:

Binding routed parameters to component input

The takeUntilDestroyed operator

Starting from Angular v16, a new operator has been introduced (developer preview) —  the takeUntilDestroyed pipeable operator.

According to the docs, the takeUntilDestroyed operator:

[…] completes the Observable when the calling context (component, directive, service, etc.) is destroyed.

takeUntilDestroyed example snippet

Unsubscribing just became a piece of cake! 🍰

However, the operator needs a destroyRef.

In the previous snippet, we are using the operator inside the constructor.

A constructor is an injection context, so the operator can grab the destroyRef it needs.

But, if we wanted to use it inside a function or a lifecycle hook, we would have to explicitly pass the destroyRef.

New reactivity with signals

The biggest change in Angular v16 was the new reactive primitive, also known as signals (developer preview).

What are signals?

A signal is a wrapper around a value, which is capable of notifying interested consumers when that value changes.

Why the need for signals?

Signals will be used as a signaling mechanism for change detection and will replace Zone.js (at some point in the future).

Why replace Zone.js?

Because performance!

You can read our previous articles about Zone.js in Angular and Angular signals to learn more.

The end goal is to be able to build completely zone-less Angular applications. But we are not there yet!

Here is a cheat sheet to get you started.

Angular signals cheat sheet

New control flow and deferred block

Starting from Angular v17 — which at the time of this writing is not yet released — a new declarative control flow will be introduced.

This brings the functionality of NgIf, NgFor, and NgSwitch into the framework itself.

Here is a quick preview of the new control flow.

New Angular template control flow

Components, directives, etc. referenced within the @defer block are loaded lazily.

In other words, deferred blocks are rendered asynchronously.

This includes all dependencies within the deferred block, like components, directives, and pipes used in those dependencies.

Conclusion

Well, don’t tell me I didn’t warn you! 😅

A lot has happened in Angular in the latest versions.

In this article, we talked about the hottest changes 🔥, such as standalone components, signals 🚦, the new control flow, and many more!

Thanks for reading.