⏱ Estimated reading time: 6 minutes

By Arthur Lannelucq, Senior Frontend developer @ Mesetys

Table of Contents

Angular went on a long journey to refresh its core components over the last versions. This path culminated in Angular 17, when the team decided to refresh the branding, their website and update the most developer-critical parts of the framework: compilation, SSR, CLI and control structures. This is a dive into the history of what led to Angular 17.

Angular 14

Angular 14 introduced Standalone components in replacement for ngComponents. Over time, features and patterns have developed around the concept such as lazy-loading and encapsulation, but this approach was dependency-heavy, hard to test, reason about and consequentially degraded user experience. SCAM, which stands for “Single Component Angular Module” developed as the easier path to develop shared modules for better reusability and easier maintenance at the risk of making the project file structure harder to grasp. This approach has been described in this blog post.

Standalone components are the official answer to this problem: every component —and this includes directives, pipes and so on— is its own module.

Also introduced in this function is the inject() method which can be used to inject dependencies outside the constructor() method. This unlocked new patterns for simplifying Route guards as demonstrated in this blog post.

Angular 15

Since Angular 15, developers can drop classes for Guards and Route Resolvers as Angular’s 14 inject() method enables them to be expressed in functional style for less code and overhead.

Another change is Directive Composition API which can be used to gather multiple Angular Directives defined as standalone into one that is the sum of their behaviors! Composing directives makes for more reusable and maintainable code.

Angular 16

This version brought many critical changes to the framework, particularly when it comes to detecting changes. Until that version, naive change detection was implemented in zone.js. This implementation was naive as any interaction would trigger a complete refresh of the app! To optimize rendering updates, a system that could better track interaction and changes was needed: Signals. Signals notify about changes to the values they are observing and only update components that depend on them.

What is the consequence of Signals for RxJS and Observables? Observable data types are often misunderstood and misused by developers, yet Signals are not meant to replace RxJS except in very specific cases that deal with synchronous changes. There are ways to convert between Observables and Signals using the toObservable() and toSignal() methods. Today, there is no other way to pass data to Signals than inputs, but their roadmap is promising with features such as using Signals as Input and Output.

On to Server Side Rendering, where hydration was destructive and caused re-renders that made the webpage blink during client-side takeover. SSR hydration could be configured as non-destructive in this version. A bigger feature concerns compilation with the introduction of Vite and esbuild that can save up to 80% of the compilation time on larger codebases! This can save massive amounts of CI/CD and developer time!

Angular Inputs can now be declared as required and their contents can be transformed automatically into booleans or numbers thanks to the booleanAttribute and numberAttribute transforms. Router parameters can also be passed directly to the components with the bindToComponentInputs option or the withComponentInputBinding RouterFeature. And we’ll finish this part on Angular 16 with self-closing tags, which has actually been introduced in 15.1 according to this changelog.

Angular 17

New branding, new logo, new documentation website with interactive components and tutorials with in-browser code experiments. This version is where high-value preview features go Generally available such as the CLI generating standalone components by default, Vite and esbuild compilations by default and a simpler SSR application creation flow. But that’s not all.

The biggest change has been introduced in the view language: developers won’t need to use the *ngIf, *ngFor and *ngSwitch directives and ng-container elements to avoid leaving traces on the DOM. To replace them, Angular introduced a new control flow in templates with @if, @for and @switch. Not only these directives don’t need to be attached to be components, but they also improve the Developer eXperience with their former alternatives: expressions are not constrained by the DOM attribute limitations, performance improved dramatically thanks to the integration with the templating language (a performance uplift of up to 90% can be observed for @for vs *ngFor!) and @for made reference tracking mandatory to track changes in lists and that is a good thing!

If lazy loading was great, its logical evolution is Deferrable Views within templates. This feature can defer the loading of dependencies like pipes, directives and CSS to further reduce initial bundle sizes and improve user experience. It requires using @defer template blocks which support conditions, placeholder contents with @placeholder, while-loading contents with @loading and on-error contents with @error. Deferrable Views support multiple triggers in an “any of” fashion and additional options for placeholder and while-loading contents.

Questions and Answers

Can @defer triggers be used to implement “all of” conditions?

This may be possible using nested @defer blocks or by using when conditions that implement that logic. Stay tuned, this feature may appear on the roadmap if it’s requested enough.

What is inject() for? Where can it be used?

inject() is to be used in injection contexts such as within constructors and field initializers. Dependency Injection allows the framework to do the dependency wiring for the developer and efficiently manage resource initialization.

Why use Standalone components?

Standalone components are a view into more composable and reusable components such that a button, an app screen and the entire application are each a component.

Signals resemble React states. What are notable differences?

While reactivity exists in Angular with Reactive Forms, they are constrained to form inputs and driven by Observables. Signals offer more control over the data and update flows. Signal updates can invoke one another and don’t significantly hamper performance.