Discover What's New in Angular 16: A User-Friendly Guide

Discover What's New in Angular 16: A User-Friendly Guide

Keeping up with the most recent technology and tools is crucial in the quickly changing field of web development. The well-known front-end framework Angular is still setting the standard for innovation as it introduces several interesting features that should make development easier, improve performance, and improve the user experience as a whole.

In this post, we'll dig into Angular 16's most notable features and examine how they're likely to change the way we create web applications. These new capabilities are intended to make your development process easier, your code more effective, and your applications more useful whether you're an experienced Angular developer or just beginning your adventure.

Standalone Components:

Standalone Components provide a simplified way to build Angular applications. Standalone components, directives, and pipes aim to streamline the authoring experience by reducing the need for NgModule


ng generate component button --inline-template --standalone

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-button',
  standalone: true,
  imports: [CommonModule],
  template: `
    <button class="app-button">
      <ng-content></ng-content>
    </button>
  `,
  styleUrls: ['./button.component.css']
})
export class ButtonComponent {

}

Angular Signals

Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates.

What are signals?

A signal is a wrapper around a value that can notify interested consumers when that value changes. Signals can contain any value, from simple primitives to complex data structures.

A signal's value is always read through a getter function, which allows Angular to track where the signal is used.

Signals may be either writable or read-only.

//Writable
count = signal(0);
//(property) App.count: SettableSignal<number>
// (alias) signal<number>(initialValue: number, equal?: ValueEqualityFn<number>): SettableSignal<number>


//Read-only
//What if there is another value that depends on the values of other signals, and needs to be recalculated 🔄 whenever any of those dependencies changes?
//In this case, we can use a computed() function to create a new signal that automatically updates whenever its dependencies change.
double = computed(() => this.count() * 2);
//(property) App.double: Signal<number>
//(alias) computed<number>(computation: () => number, equal?: ValueEqualityFn<number>): Signal<number>

Signals are primitives, so we can use them outside the component class

const myValue = signal<number>(10000);

The signal function is a TypeScript function that creates a Signal. It takes two arguments:

  • initialValue : Represent the initial value of the signal, and it can be of any type T

  • options is an object of the CreateSignalOptions type that includes an equal method for comparing two values of type T. If the options object is not provided when creating a signal, the defaultEquals function will be used instead. The defaultEquals function compares two values of the same type T using a combination of the === operator and the Object.is method

The signal function returns a WritableSignal<T>. Signal are a getter function, but the type WritableSignal give us the possibility to modify the value by three methods :

set [set(value: T): void] for replacement (set the signal to a new value, and notify any dependents)

this.count.set(0);

update[update(updateFn: (value: T) => T)] for deriving a new value (Update the value of the signal based on its current value, and notify any dependents), The update operation uses the set() operation for performing updates behind the scenes.

this.count.update(number => number + 1)

mutate [mutate(mutatorFn: (value: T) => void)] for performing internal mutation of the current value (Update the current value by mutating it in-place, and notify any dependents)

const todos = signal([{title: 'Learn signals', done: false}]);

todos.mutate(value => {
  // Change the first TODO in the array to 'done: true' without replacing it.
  value[0].done = true;
});

Signal is not just a value that can be modified, it is more than that, Signal is a reactive value 🔃 and is a producer that notifies consumers(dependents) when it changes.

So dependents in Signal are any code that has registered an interest in the Signal’s value and wishes to be notified whenever the Signal’s value changes. When the Signal’s value is modified, the Signal will notify all of its dependents, allowing them to react to the change in the Signal’s value. This makes the Signal is the core element of a reactive application, as it allows different parts of the application to automatically update in response to changes in data.

effect

Sometimes, when a signal has a new value, we may need to add a side effect. To accomplish this, we can use the effect() function.

Effect schedules and runs a side-effectful function inside a reactive context.

export function effect(
    effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef

The function inside the effect will re-evaluate with any change that occurs in the signals called inside it. Multiple signals can be added to the effect function.

when we declare an effect function, the effectFn passed as an argument will be added to the list of the consumers of any signals used by the effectFn, such as movies in our example. (Signals used by the effectFn will be the producers).

Then, when the signal has a new value by using the set, update, or mutate operators, the effectFn will be re-evaluated with the new signal value(The producer notifies all consumers of the new values).

constructor() {
    effect(() => {
      console.log('Count changed', this.count());
      console.log('Count doubled', this.double());
    });
  }

The effect() function returns an EffectRef, which is a global reactive effect that can be manually destroyed. An EffectRef has a destroy operation.

Use cases for effects

Effects are rarely needed in most application code but may be useful in specific circumstances. Here are some examples of situations where an effect might be a good solution:

  • Logging data being displayed and when it changes, either for analytics or as a debugging tool

  • Keeping data in sync with window.localStorage

  • Adding custom DOM behavior that can't be expressed with template syntax

  • Performing custom rendering to a <canvas>, charting library, or other third-party UI library

Destroying effects

When you create an effect, it is automatically destroyed when its enclosing context is destroyed. This means that effects created within components are destroyed when the component is destroyed. The same goes for effects within directives, services, etc.

Effects return an EffectRef that can be used to destroy them manually, via the .destroy() operation. This can also be combined with the manualCleanup option to create an effect that lasts until it is manually destroyed. Be careful to actually clean up such effects when they're no longer required.

When not to use effects

Avoid using effects for the propagation of state changes. This can result in ExpressionChangedAfterItHasBeenChecked errors, infinite circular updates, or unnecessary change detection cycles.

Because of these risks, setting signals is disallowed by default in effects, but can be enabled if necessary.