What is new in Angular 16?

Let’s find out what is new in Angular 16 and take a look at how to use the new features. With the release of Angular 16, developers can expect significant improvements in server-side rendering, runtime performance, and component authoring experience. In this blog post, we will discuss the key features of Angular 16 and how they will impact the development of web applications.

What is new in Angular 16
What is new in Angular 16

Table of Contents

Signals:

Signals are a new way of managing state changes in Angular applications, inspired by Solid.js. Signals are functions that return a value (get()) and can be updated by calling them with a new value (set()). Signals can also depend on other signals, creating a reactive value graph that automatically updates when any dependencies change. Signals can be used with RxJS observables, which are still supported in Angular v16, to create powerful and declarative data flows.

Why Signals?

Angular applications use a lot of data that needs to be shared across components. The traditional way of managing this data is through a shared service or event bus. However, this approach can become difficult to manage as the application grows in complexity. Signals provide a more declarative and reactive way of managing data, making it easier to reason about and debug.

How to Use Signals in Angular 16?

To use signals in Angular 16, we need to first install the @ngneat/signal package:

npm install @ngneat/signal

Once installed, we can import the Signal class from the @ngneat/signal package

import { Signal } from '@ngneat/signal';

Creating a Signal:

To create a signal, we can simply instantiate the Signal class and provide an initial value:

const counterSignal = new Signal<number>(0);

We can then use this signal in our components by subscribing to its value changes:

export class CounterComponent implements OnInit {
  count: number;

  constructor(private counterSignal: Signal<number>) {}

  ngOnInit() {
    this.counterSignal.subscribe((value) => {
      this.count = value;
    });
  }

  increment() {
    this.counterSignal.set(this.count + 1);
  }
}

In this example, we inject the counterSignal into the CounterComponent constructor and subscribe to its value changes in the ngOnInit method. We then use the increment method to update the value of the counterSignal by calling its set method with a new value.

Using Signals with RxJS:

Signals can also be used in conjunction with RxJS observables to create more powerful data flows. For example, we can create a signal that represents the current user and use an observable to update the user data from an API

const userSignal = new Signal<User>(null);
const user$ = this.http.get<User>('/api/user');

user$.subscribe((user) => {
  userSignal.set(user);
});

Signals are a powerful new feature in Angular 16 that provide a declarative and reactive way of managing data. They can be used to simplify the management of data in Angular applications, making it easier to reason about and debug. Signals are also composable and testable, making them a valuable tool for developers.

Reactivity Model and Zone.js:

One of the most anticipated features of Angular 16 is the revisiting of the reactivity model and making Zone.js optional to improve runtime performance. Zone.js is a library that monkey-patches browser APIs to detect changes and trigger change detection in Angular applications. While this makes Angular easy to use and write, it also adds some overhead and complexity to the framework. In Angular 16, Zone.js will be optional, meaning developers can opt out of it and use other ways of managing reactivity, such as RxJS or signals.

To enable the new reactivity model in Angular 16, developers need to provide a new option in their tsconfig.json file:

{
  "compilerOptions": {
    // ...
    "angularCompilerOptions": {
      "enableIvy": true,
      "enableZoneLess": true // enable the new reactivity model
    }In Angular 16, the introduction of the required component inputs feature will greatly improve the developer experience and code quality of Angular applications. With this feature, developers can mark some inputs of a component as required, which means that the parent component must provide them, or else an error will be thrown.

Here's an example to demonstrate how the required component inputs feature works:.stk-10c102e{margin-top:35px !important;margin-bottom:-10px !important}.stk-10c102e .stk-block-subtitle__text{font-size:22px !important}@media screen and (max-width:1023px){.stk-10c102e .stk-block-subtitle__text{font-size:22px !important}}Reactivity Model and Zone.js:.stk-10c102e{margin-top:35px !important;margin-bottom:-10px !important}.stk-10c102e .stk-block-subtitle__text{font-size:22px !important}@media screen and (max-width:1023px){.stk-10c102e .stk-block-subtitle__text{font-size:22px !important}}Reactivity Model and Zone.js:
  }
}

Required Component Inputs

In Angular 16, the introduction of the required component inputs feature will greatly improve the developer experience and code quality of Angular applications. With this feature, developers can mark some inputs of a component as required, which means that the parent component must provide them, or else an error will be thrown.

Here’s an example to demonstrate how the required component inputs feature works:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  template: `
    <h2>{{ name }}</h2>
    <p>{{ bio }}</p>
  `
})
export class UserProfileComponent {
  @Input() name: string;
  @Input({ required: true }) bio: string;
}

In this example, the UserProfileComponent requires a name and a bio input. However, the bio input has been marked as required by passing { required: true } to the @Input decorator.

If the parent component fails to provide the bio input, the Angular compiler will throw an error at compile time, indicating that the input is required but not provided.

This feature not only helps catch bugs and typos at compile time, but also ensures that components receive all the necessary data to function properly. It also makes components more self-documenting and easier to use, as developers can easily see which inputs are required and which are optional.

Overall, the required component inputs feature in Angular 16 is a welcome addition that will improve code quality and make Angular applications more robust.

Non-Destructive Hydration

In Angular 16, the introduction of non-destructive hydration is a significant improvement to server-side rendering. Hydration is the process of converting server-side rendered HTML content into a fully interactive and functional web page on the client-side by attaching JavaScript behavior and event listeners. This process helps reduce the time-to-interactive (TTI), which is the time it takes for the web page to become interactive and usable by the user.

Hydration has been a common feature in other web development frameworks like React or Next.js for some time. However, in Angular, it was not easy to implement this process. With Angular 16, the framework now supports non-destructive hydration out of the box.

Non-destructive hydration allows for the reuse of the server-side Document Object Model (DOM) instead of re-rendering it. This technique helps improve performance by reducing the amount of work the browser needs to do. Instead of re-rendering the DOM, non-destructive hydration only attaches event listeners and creates the data structures required by the Angular runtime. This feature is especially useful for Single Page Applications (SPAs) and other applications that require server-side rendering.

Here is an example of how to implement non-destructive hydration in Angular 16:

// app.server.module.ts
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ServerTransferStateModule,
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

Here we import the ServerModule and ServerTransferStateModule from @angular/platform-server. We then bootstrap the AppComponent.

The ServerModule provides a way to compile the application on the server-side, while the ServerTransferStateModule helps transfer the state of the application from the server to the client.

In the main.ts file, we can then use the renderModuleFactory method to render the application on the server-side:

// main.ts
import { enableProdMode } from '@angular/core';
import { renderModuleFactory } from '@angular/platform-server';
import { AppServerModuleNgFactory } from './app.server.module.ngfactory';

const document = '<html><head></head><body><app-root></app-root></body></html>';
const url = '/';
const extraProviders = [];

enableProdMode();

renderModuleFactory(AppServerModuleNgFactory, {
  document,
  url,
  extraProviders,
}).then(html => {
  console.log(html);
});

Here, we use the renderModuleFactory method to render the AppServerModuleNgFactory with the provided document, URL, and extra providers. The resulting HTML can be logged to the console or sent back to the client.

Overall, non-destructive hydration is a significant improvement to server-side rendering in Angular 16. It helps reduce the time-to-interactive, improves SEO and accessibility, and makes server-side rendering easier to implement in Angular applications.

DestroyRef

In Angular 16, a new feature called DestroyRef is being introduced that allows developers to access the destroy context of a component. This can be useful for cleanup tasks that must be performed when a component is destroyed.

The DestroyRef is a service that is provided at the component level. It is used to register cleanup tasks that need to be performed when the component is destroyed. The DestroyRef service is injected into the component, and the component can use it to register callbacks that will be called when the component is destroyed.

Here is an example of how to use DestroyRef in a component:

import { Component, OnInit, OnDestroy, Injectable } from '@angular/core';
import { DestroyRef } from './destroy-ref.service';

@Component({
  selector: 'app-my-component',
  template: '<h1>Hello, World!</h1>',
  providers: [DestroyRef]
})
@Injectable()
export class MyComponent implements OnInit, OnDestroy {

  constructor(private destroyRef: DestroyRef) {}

  ngOnInit(): void {
    this.destroyRef.onDestroy(() => {
      console.log('Component destroyed!');
    });
  }

  ngOnDestroy(): void {
    console.log('Destroying component!');
  }

}

In this example, we import the DestroyRef service and inject it into our component. In the ngOnInit method, we register a callback function that will be called when the component is destroyed using the onDestroy method provided by the DestroyRef service.

When the component is destroyed, the ngOnDestroy method is called, which logs a message to the console indicating that the component is being destroyed.

The DestroyRef service can be useful in a number of scenarios. For example, if your component has any open subscriptions or other resources that need to be cleaned up when the component is destroyed, you can use the DestroyRef service to register a callback function that will perform the cleanup.

Here’s an example of how to use the DestroyRef service to clean up an open subscription:

import { Component, OnInit, OnDestroy, Injectable } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { DestroyRef } from './destroy-ref.service';

@Component({
  selector: 'app-my-component',
  template: '<h1>Hello, World!</h1>',
  providers: [DestroyRef]
})
@Injectable()
export class MyComponent implements OnInit, OnDestroy {

  subscription: Subscription;

  constructor(private destroyRef: DestroyRef) {}

  ngOnInit(): void {
    this.subscription = new Observable(observer => {
      observer.next('Hello!');
    }).subscribe(value => {
      console.log(value);
    });
    this.destroyRef.onDestroy(() => {
      this.subscription.unsubscribe();
    });
  }

  ngOnDestroy(): void {
    console.log('Destroying component!');
  }

}

In this example, we create a new Observable that emits a single value and subscribe to it. We also use the onDestroy method provided by the DestroyRef service to register a callback function that will unsubscribe from the subscription when the component is destroyed.

The DestroyRef service is a powerful tool that can be used to improve the reliability and performance of your Angular applications. By allowing you to easily perform cleanup tasks when a component is destroyed, the DestroyRef service can help you avoid memory leaks and other issues that can arise when resources are not properly cleaned up.

Design Tokens

Design tokens are a powerful concept that provide developers with greater flexibility and consistency when customizing existing Material components in Angular applications. In Angular 16, the Material team at Google has introduced a new design token system that will allow developers to create finely-tuned components without worrying about frequent changes.

Design tokens are essentially variables that represent a particular aspect of a design system, such as colors, typography, spacing, and more. These variables are defined in a single source of truth and can be reused throughout the entire application. This makes it easier for developers to maintain consistency and make global changes to the design system without having to manually update every instance of the affected component.

To implement design tokens in an Angular application, the first step is to define the tokens in a separate file, which can be imported and used throughout the application. Let’s take a look at an example of defining color tokens:

// design-tokens.ts
export const primaryColor = '#007bff';
export const secondaryColor = '#6c757d';
export const successColor = '#28a745';
export const dangerColor = '#dc3545';
export const warningColor = '#ffc107';
export const infoColor = '#17a2b8';

In this example, we have defined six different color tokens that can be used to style various components in the application. Once we have defined the tokens, we can use them in our stylesheets by referencing them using the var() function:

// app.component.scss
@import 'design-tokens';

.button {
  background-color: var(--primary-color);
  color: white;
}

.alert {
  background-color: var(--danger-color);
  color: white;
}

In this example, we are using the primary-color and danger-color tokens to style a button and an alert component respectively. The var() function references the token value defined in the design-tokens.ts file, making it easy to update the colors globally by simply modifying the token values in the source file.

Design tokens not only provide consistency and ease of maintenance but also allow for greater customization and flexibility in styling components. Developers can use the same token to define multiple styles with different shades, saturations, or opacities, creating a unique and cohesive design system.

For example, let’s take a look at how we can define different shades of the primary color token using CSS variables:

// design-tokens.ts
export const primaryColor = {
  100: '#cce5ff',
  200: '#99c2ff',
  300: '#66a3ff',
  400: '#007bff',
  500: '#005cbf',
  600: '#004499',
  700: '#002266',
  800: '#001a33',
  900: '#000d1a',
};

In this example, we have defined the primary color token as an object with keys representing different shades of the color. We can then reference these keys using CSS variables to create different styles:

// app.component.scss
@import 'design-tokens';

.button {
  background-color: var(--primary-color-500);
  color: white;
}

.button:hover {
  background-color: var(--primary-color-600);
}

In this example, we are using the primary-color-500 key to define the background color of a button, and the primary-color-600 key to define the hover color. This allows us to create a consistent and cohesive design system with minimal effort.

In summary, design tokens are a powerful tool for maintaining consistency and flexibility in Angular applications. By defining global variables for aspects of a design system, developers can create finely-tuned components that are

Standalone Components

Angular has always had a steep learning curve, with its emphasis on modularity and component-based architecture. To make Angular more accessible to new developers and make it easier for developers to build standalone apps, the Angular team introduced a new feature called standalone components.

Standalone components are Angular components that can be used without being part of a larger application or module. They can be created and used independently, without having to be defined as part of an NgModule. This makes it easier to create reusable components that can be used across different applications.

To create a standalone component, you simply add the standalone: true property to the @Component decorator in your component class:

@Component({
  selector: 'app-custom-button',
  templateUrl: './custom-button.component.html',
  styleUrls: ['./custom-button.component.scss'],
  standalone: true
})
export class CustomButtonComponent implements OnInit {
  // Component logic
}

By adding the standalone: true property to the @Component decorator, you tell Angular that this component is intended to be used independently. This means that you don’t need to include it in an NgModule, and you can use it anywhere in your application just by importing it like any other component.

For example, suppose you have a standalone component called CustomButtonComponent. You can use it in any other component or module like this:

import { CustomButtonComponent } from './custom-button.component';

@Component({
  selector: 'app-home',
  template: `
    <h1>Welcome to my app!</h1>
    <app-custom-button></app-custom-button>
  `
})
export class HomeComponent {
  // Component logic
}

In this example, we import the CustomButtonComponent and use it in the HomeComponent template. We don’t need to import any modules or add any declarations to an NgModule, because the CustomButtonComponent is a standalone component that can be used independently.

Using standalone components can simplify your code and make it easier to reuse components across different parts of your application. It also makes it easier for new developers to learn Angular, as they can start building components without having to understand the full NgModule system.

CLI Simplification

Angular CLI, or Command Line Interface, is a powerful tool that simplifies the development process of Angular applications. With the release of Angular 16, the CLI is getting an overhaul to make it even more efficient and user-friendly.

The Angular CLI simplification effort aims to reduce the conceptual overhead of the CLI and improve the project structure to make it easier for developers to navigate and work with Angular projects. This will help developers to spend less time figuring out how to use the CLI and more time building their applications.

One of the main changes in the Angular CLI is the introduction of a new configuration file called “angular.json”. This file replaces the old “.angular-cli.json” file and provides a more flexible and modular way of configuring your Angular projects. The new configuration file is easier to read and allows you to easily configure your project structure, including the styles, assets, and build configurations.

Another significant change is the removal of the “e2e” folder from the default project structure. Instead of the “e2e” folder, developers can now use the “integration” folder to house integration tests. This change aims to simplify the project structure and make it more intuitive.

The CLI simplification effort also includes improvements to the “ng generate” command, which is used to create new components, services, modules, and more. The new version of the command provides better error handling and messaging, as well as improved support for custom templates.

In addition, the CLI simplification effort aims to improve the overall performance of the CLI, reducing the time it takes to perform common tasks such as building, testing, and deploying Angular applications. This will help developers to be more productive and efficient in their work.

Here’s an example of how the new configuration file looks like in Angular 16:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "my-app": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/my-app",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            }
          }
        }
      }
    }
  },
  "defaultProject": "my-app"
}

As you can see, the new configuration file is much more structured and easier to read than the old one. It provides a clear hierarchy of settings and allows you to easily modify the settings.

Overall, the simplification of the Angular CLI in Angular 16 is a much-welcomed improvement that will make it easier for developers to work with Angular and build better applications. The improvements to the project structure and JavaScript bundles will also help developers to build faster and more efficient applications, which is important in today’s competitive landscape.

Improved Documentation and Learning Experience

Improved documentation and learning experience is another area that the Angular team is focused on in Angular 16. As the framework grows, it becomes more complex, and it’s essential to have clear and concise documentation that developers can use to learn the ins and outs of the framework.

One of the significant improvements that the Angular team is making is creating more engaging and digestible tutorials that will help developers learn Angular more efficiently and enjoyably. These tutorials will be designed to teach developers everything they need to know about Angular, from the basics to more advanced concepts.

Another improvement that the Angular team is making is in the area of schematics. Schematics are a way to automate tasks in Angular, such as generating new components, services, and modules. The Angular team is creating new schematics that will make it easier for developers to work with standalone components and modules. The new schematics will help developers bootstrap new projects quickly and easily, with all the necessary dependencies and configurations in place.

Additionally, the Angular team is creating an ng new collection, which will be a set of schematics that developers can use to bootstrap new projects. The ng new collection will include all the necessary configurations and dependencies, making it easy for developers to get started with Angular.

To improve documentation, the Angular team is also working on refactoring existing documentation, making sure that all the content fits into a consistent set of content types. This will make it easier for developers to find the information they need and ensure that the documentation is consistent and accurate.

Finally, the Angular team is creating new tools and resources that developers can use to learn Angular, such as interactive tutorials, videos, and podcasts. These resources will be designed to help developers learn Angular in a way that is engaging, fun, and easy to understand.

Watch the video below for a brief overview of ‘What is new in Angular 16

Conclusion

Angular 16 is an exciting release that brings a lot of new features and improvements to the table. The introduction of signals, fine-grained reactivity, and required component inputs will make it easier for developers to build better and faster applications with less effort. The flattening of the Angular learning path, simplification of component authoring experience, and improvements to the Angular CLI will make it easier for new developers to get started with the framework and existing developers to work more efficiently.

Additionally, the introduction of non-destructive hydration will improve the performance of server-side rendering applications, making them faster and smoother. These improvements will not only benefit developers but also end-users who will enjoy faster and more responsive applications.

In summary, Angular 16 is a major release that brings a lot of new features and improvements to the framework. It focuses on simplification and improved learning experiences for developers, making it easier to build better and faster applications. With Angular 16, developers will be able to take their skills to the next level and create applications that are more efficient, performant, and user-friendly. So, whether you are a new or an experienced developer, Angular 16 is definitely worth keeping an eye on!