🎯Key updates and novel capabilities
Angular 16 arrives with a set of significant updates. The following list covers the most notable additions and changes:
- Signals
DestroyReftakeUntilDestroyed- Required inputs
- Routing data bound to component inputs
- Node.js v14 support has been dropped
- TypeScript 5.0 Support
- The Angular Compatibility Compiler (ngcc) has been removed
- Server-side rendering now includes non-destructive hydration, allowing the client to reuse server-side request results
- Enhanced strict type checking for
ngTemplateOutlet - The
provideServiceWorkerfunction for registering service workers in standalone apps - Experimental Jest support
Additionally, Angular 16 introduces a new development server built on Vite and Esbuild.
While reading articles on these changes is useful, you should also consult:
- the official documentation, and
- the Angular changelogs
for deeper insights.
📌Signals
Official docs: Angular Signals
GitHub discussion: Angular Reactivity with Signals
RFC: RFC: Angular Signals
PR: feat(core): add Angular Signals to the public API
PR: Prototype of the RxJS interop layer for signals
PR: feat(core): Mark components for check if they read a Signal
Angular introduces a new primitive type called a "Signal". This type stores a value much like a standard variable, but it also informs any interested consumers whenever that value changes. Signals can hold both primitive data types and objects.
A "Computed Signal" derives its value from other Signals. To create one, you use the computed function and provide a derivation function that specifies how the value is calculated.
If a component uses the OnPush change detection strategy and includes a Signal in its template, Angular tracks that Signal as a dependency for the component. When the Signal's value changes, Angular marks the component for check. This behavior will evolve in Angular 17, which will support Signal-based per-component change detection without requiring zone.js.
For an introduction to Signals and an overview of what's new in Angular 16 and what's planned for Angular 17, check out Pawel Kozlowski’s talk at NgBe:
Deborah Kurata has authored a comprehensive tutorial on Signals. Her article covers the rationale for Signals, explains what they are, and walks through the process of creating, reading, and updating them. She also demonstrates computed Signals and effects.
Manfred Steyer provides a comparison between the current Zone.js-based change detection and the new Signal-based approach.
For details on how Signals and RxJS work together, Stefan Haas explains Signal and RxJS interop.
If you want to understand the mechanics of Signals using the push & pull model, Tomas Trajan has published an explanation that covers these concepts.
📌DestroyRef
Official docs: DestroyRef
Commit: feat(core): introduce concept of DestroyRef
DestroyRefprovides a way to register callbacks that execute for any cleanup or destruction behavior.
Example:
@Component({
selector: 'component-1',
standalone: true,
template: ``,
})
class Component1 {
constructor(private destroyRef: DestroyRef) {
destroyRef.onDestroy(() => {
// run this cleanup code, when the component is destoryed
});
}
}
In a follow-up article, Netanel Basal demonstrates how to build a reusable cleanup routine that runs when a given scope is destroyed.
📌takeUntilDestroyed
Commit: feat(core): implement takeUntilDestroyed in rxjs-interop
The takeUntilDestroyed operator completes an observable when the associated component, directive, service, or pipe is torn down.
Consider this example: when the component initializes, it subscribes to a 2-second interval() and logs 'Tick' messages to the browser console. When the component gets destroyed, it automatically unsubscribes from that interval():
@Component({
selector: 'component-1',
standalone: true,
template: ``,
})
class Component1 {
constructor() {
interval(2000).pipe(takeUntilDestroyed()).subscribe(() => console.log('Tick'));
}
}
You can also provide a specific DestroyRef as an argument to takeUntilDestroyed, which allows you to build reusable functions that clean up their subscriptions when the relevant context is destroyed:
@Component({
selector: 'component-1',
standalone: true,
template: ``,
})
class Component1 {
constructor(private destroyRef: DestroyRef) {
reusableTicks(destroyRef);
}
}
📌Required inputs
Official docs: Input
PR: feat(compiler): add support for compile-time required inputs
It is now possible to declare component or directive inputs as required. The snippet below will cause a compile-time error because the template for Component2 does not provide the required text input:
@Component({
selector: 'component-1',
standalone: true,
template: ``,
})
class Component1 {
// ❗ this is a required input
@Input({ required: true }) text: string;
}
@Component({
selector: 'component-2',
standalone: true,
template: `
<component-1></component-1>
`,
imports: [Component1]
})
class Component2 {
constructor(private destroyRef: DestroyRef) {
destroyRef.onDestroy(() => {
// run this cleanup code, when the component is destoryed
});
}
}
📌Binding Router data to component inputs
Official docs: Router / Getting route information, withComponentInputBinding
PR: Feature to bind Router information to component inputs
This new capability makes it much easier to access route information within components. Enea Jahollari explains the feature through practical examples in a dedicated article.
📌Removal of Node.js v14 support
Changelog entry: Node.js v14 support has been removed
Node.js v14 is scheduled for End-of-Life on 2023–04–30. Angular v16 will no longer support this version, but will continue to officially support Node.js v16 and v18.
📌TypeScript 5
For Angular developers, TypeScript 5 brings two particularly relevant additions: the updated decorator implementation and the ability to extend multiple configuration files through tsconfig.json.
Decorators
TypeScript 5 now aligns with the ECMAScript decorator proposal, introducing what are commonly called 'Standard decorators'.
Kevin Kreuzer dives into the mechanics of these new decorators in his article.
Multiple configuration file support in tsconfig.json‘s extends
The extends property in tsconfig.json now accepts an array of paths. In the example below, the resulting configuration has both strict and noImplicitReturns turned on:
// tsconfig-a.json
{
"compilerOptions": {
"strict": true
}
}
// tsconfig2-b.json
{
"compilerOptions": {
"noImplicitReturns": true
}
}
// tsconfig.json
{
"extends": ["./tsconfig-a.json", "./tsconfig-b.json"],
"files": [
"src/main.ts",
"src/polyfills.ts"
]
}
The full list of new TypeScript capabilities is available in their official announcement.
📌Angular Compatibility Compiler (ngcc) has been removed
Changelog entry: Angular Compatibility Compiler (ngcc) has been removed
Angular 9 marked the shift from the “View Engine” to “Ivy” as the default rendering pipeline. To bridge the gap for libraries not yet migrated, the Angular Compatibility Compiler (ngcc) was introduced. Starting with Angular 16, ngcc along with all remnants of the “View Engine” have been purged, meaning libraries built on the old architecture are no longer compatible with v16 and beyond.
For a deeper look at how “View Engine” and “Ivy” differ, check out Maria Korneeva‘s explanation of both rendering engines.
📌NgZone is configurable in bootstrapApplication
Official docs: provideZoneChangeDetection
PR: Add ability to configure NgZone in bootstrapApplication
Angular 16 introduces two changes related to zone configuration:
- A custom zone implementation can now be supplied through
bootstrapApplication - NgZone gains two new configurable flags:
eventCoalescingandrunCoalescing, both designed to cut down on unnecessary change detection cycles.
class CustomZone extends NoopNgZone {}
bootstrapApplication(
StandaloneCmp, {
providers: [
// provide a custom zone implementation
{ provide: NgZone, useValue: new CustomZone() }
]
}
);
bootstrapApplication(
StandaloneCmp, {
providers: [
// enable eventCoalescing and runCoalescing
provideZoneChangeDetection({
eventCoalescing: true,
runCoalescing: true
})
]
});
📌Server Side Rendering improvements
Non-destructive hydration
PR: feat(platform-browser): add a public API function to enable non-destructive hydration
With non-destructive hydration, Angular can now reuse the DOM structure generated on the server instead of rebuilding it on the client. This approach eliminates the visual flicker that often appears as the application becomes interactive.
The results of the request done on the server side can be reused on the client side
PR: feat(platform-browser): enable HTTP request caching when using provideClientHydration
To spare the client from re-sending the same network requests, responses obtained during server-side rendering are now cached and made available to the client-side code.
Jessica Janiuk outlines where server-side rendering stands today and where it’s heading in her blog post.
📌Strict type checking for ngTemplateOutlet
PR: fix(common): strict type checking for ngtemplateoutlet
In earlier versions, the context passed to ngTemplateOutlet was typed simply as Object, so the following snippet compiled without any complaints:
interface Context1 {
prop: number;
}
@component({
standalone: true,
imports: [NgTemplateOutlet],
selector: 'person',
template: `
<ng-container
*ngTemplateOutlet="
myTemplateRef;
context: { prop: 1, missingProp: 2 }
"></ng-container>
`,
})
export class Component1 {
templateRef!: TemplateRef<Context1>;
}
Angular 16 now raises a compile-time error for the same code, since missingProp does not exist on Context1. To resolve this, either add the missingProp property to Context1 or wrap the expression with $any(…) inside the template to keep the old behavior.
📌provideServiceWorker function to register service workers in standalone applications
Official docs: Service workers / provideServiceWorker
PR: feat(service-worker): add function to provide service worker
Registering a service worker in a standalone application previously required the awkward importProvidersFrom(ServiceWorkerModule.register(…)) approach.
The newly added provideServiceWorker offers a much cleaner way to accomplish the same task, as shown here:
bootstrapApplication(AppComponent, {
providers: [
provideServiceWorker('my-service--worker.js')
],
});
📌Experimental Jest support
The Angular Team introduced the first experimental Jest support in Angular 16. Looking ahead to Angular 17, they plan to swap out the deprecated Karma test runner for the Web Test Runner.
➕ Have you seen any other resources I should add to this Angular 16 Study Guide? Please send it to me so that I can feature it in the article!
👨💻About the author
I'm Gergely Szerovay, a frontend development chapter lead. Angular is something I genuinely enjoy both teaching and learning. Every day, I take in as much Angular-related material as I can — whether it's articles, podcasts, or conference talks.
I launched the Angular Addict Newsletter to share the best resources I find each month. Whether you're just getting started or you're already deep into Angular, there's something in it for you.
Alongside the newsletter, I run a publication called Angular Addicts. It's a curated list of the resources I find most insightful. If you're interested in contributing as a writer, feel free to reach out.
Let's keep learning Angular together! Subscribe here 🔥
You can also follow me on Medium, Twitter, or LinkedIn for more Angular insights!
