Angular Signals — Reactive programming redefined

Angular 17 introduces a powerful new feature called Signals, which provides an efficient and reactive approach to state management in your applications. This blog post will delve into what Signals are and demonstrate their usage with a real-world example: a Todo List application.

What are Signals?

Signals in Angular 17 represent a new reactive state management mechanism. They allow you to create reactive values that automatically update any dependent computations whenever the state changes. This makes it easier to manage UI state and create reactive user interfaces with less boilerplate code and improved performance.

Key Benefits of Signals
  • Automatic Updates: Signals propagate changes automatically to any dependent computations, reducing the need for manual state management and ensuring that the UI stays in sync with the state.
  • Declarative Syntax: The declarative nature of Signals makes the code easier to read, understand, and maintain.
  • Improved Performance: Signals are designed to be efficient, minimizing unnecessary re-renders and computations, which leads to better performance.

In this example, we’ll explore a simple counter component to demonstrate how to use Signals and the computed function in Angular 17.

import { Component } from '@angular/core';
import { signal, computed } from '@angular/signals';

@Component({
  selector: 'app-signal-example',
  template: `
    <div>
      <button (click)="increment()">Increment</button>
      <p>Counter: {{ counter() }}</p>
      <p>Double: {{ doubleCounter() }}</p>
    </div>
  `
})
export class SignalExampleComponent {
  counter = signal(0);
  doubleCounter = computed(() => this.counter() * 2);

  increment() {
    this.counter.set(this.counter() + 1);
  }
}

How it works?

  • Initialization:

    • The component initializes the counter signal to 0.
  • Reactivity:

    • When the button is clicked, the increment method updates the counter signal.
    • The doubleCounter computed value automatically recalculates because it depends on counter.
  • Automatic Updates:

    • The component’s template displays the current values of counter and doubleCounter.
    • When counter changes, Angular automatically updates the displayed values, ensuring the UI is always in sync with the state.

Real-World Example: Todo List Application

Let’s build a simple Todo List application to demonstrate the usage of Signals in Angular 17.

Step 1: Setting Up the Angular Project

First, create a new Angular project if you haven’t already:


ng new angular-signals-todo
cd angular-signals-todo
ng serve
Step 2: Creating the Todo Service with Signals

We’ll create a TodoService to manage our todo list state using Signals.

// src/app/todo.service.ts
import { Injectable, signal } from '@angular/core';

interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

@Injectable({
  providedIn: 'root'
})
export class TodoService {
  private nextId = 1;
  todos = signal<Todo[]>([]);

  addTodo(title: string) {
    const newTodo: Todo = { id: this.nextId++, title, completed: false };
    this.todos.update(todos => [...todos, newTodo]);
  }

  toggleTodoCompletion(id: number) {
    this.todos.update(todos =>
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  }

  removeTodo(id: number) {
    this.todos.update(todos => todos.filter(todo => todo.id !== id));
  }
}

In this service:

  • We define a Todo interface to structure our todo items.
  • The todos signal is initialized as an empty array.
  • Methods addTodo, toggleTodoCompletion, and removeTodo are provided to manipulate the todos state.
Step 3: Creating the Todo Component

Now, let’s create a component to interact with our TodoService and display the todo list. We’ll make this a standalone component to take advantage of Angular 17’s standalone components feature.

// src/app/todo.component.ts
import { Component } from '@angular/core';
import { TodoService } from '../todo.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.css'],
  standalone: true,
  providers: [TodoService],
  imports: [CommonModule, FormsModule],
})
export class TodoComponent {
  newTodoTitle = '';

  constructor(public todoService: TodoService) {}

  addTodo() {
    if (this.newTodoTitle.trim()) {
      this.todoService.addTodo(this.newTodoTitle.trim());
      this.newTodoTitle = '';
    }
  }

  toggleCompletion(id: number) {
    this.todoService.toggleTodoCompletion(id);
  }

  removeTodo(id: number) {
    this.todoService.removeTodo(id);
  }
}
Step 4: Creating the Todo Component Template

Next, create a template for the TodoComponent to display and interact with the todo list.


<!-- src/app/todo.component.html -->
<div class="todo-app">
  <h1>Todo List</h1>
  <input
    type="text"
    [(ngModel)]="newTodoTitle"
    placeholder="What needs to be done?"
    (keyup.enter)="addTodo()"
  />
  <button (click)="addTodo()">Add</button>

  <ul>
    <ul>
      @for (todo of todoService.todos();track todo.id) {
      <li>
        <input
          type="checkbox"
          [checked]="todo.completed"
          (change)="toggleCompletion(todo.id)"
        />
        <span [class.completed]="todo.completed">{{ todo.title }}</span>
        <button (click)="removeTodo(todo.id)">Remove</button>
      </li>
      }
    </ul>
  </ul>
</div>
Step 5: Adding Styles

Add some basic styles for the TodoComponent.


/* src/app/todo.component.css */
.todo-app {
  max-width: 400px;
  margin: 0 auto;
  text-align: center;
}

.completed {
  text-decoration: line-through;
  color: grey;
}
Step 6: Adding the Todo Component to the App Module

Register the TodoComponent in your AppModule.


import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { TodoComponent } from './todo/todo.component';

bootstrapApplication(TodoComponent);
Step 7: Using the Todo Component in the App Component

Use the TodoComponent in your index.html.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Angular 17 Signal Demo</title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <app-todo></app-todo>
  </body>
</html>

Demo

Conclusion

With Angular 17’s Signals, you can create reactive state management with less boilerplate and improved performance. In this example, we demonstrated how to build a Todo List application using Signals to manage the state of the todos. This approach ensures that the UI remains responsive and efficient, automatically updating whenever the state changes. As you explore Angular 17 further, you’ll find that Signals provide a powerful and intuitive way to handle state in your applications.

Previous Post
No Comment
Add Comment
comment url