Skip to main content
The Dragonball pages demonstrate advanced signal usage with arrays, form handling, and Angular’s new control flow syntax for conditional rendering and loops.

Overview

This project includes two similar components that showcase character list management:
  • DragonballPage - Basic implementation with one character
  • DragonballSuperPage - Enhanced version with two characters
Both components share the same structure and functionality, demonstrating how to work with signal arrays and complex state updates.

DragonballPage Component

Located at src/app/pages/dragonball/

TypeScript Implementation

dragonball-page.ts
import { Component, signal } from '@angular/core';

interface Character {
  id: number;
  name: string;
  power: number;
}

@Component({
  templateUrl: './dragonball-page.html',
})
export class DragonballPage {

  name = signal('');
  power = signal(0);

  characters = signal<Character[]>([
    { id: 1, name: 'Goku', power: 9001 },
//    { id: 2, name: 'Vegeta', power: 8000 },
//    { id: 3, name: 'Piccolo', power: 3000 },
//    { id: 4, name: 'Yamcha', power: 500 },
  ]);

  addCharacter() {
    // Validación correcta
    if (!this.name().trim() || this.power() <= 0) {
      return;
    }

    const newCharacter: Character = {
      id: this.characters().length + 1,
      name: this.name(),
      power: this.power(),
    };

    // Actualización correcta de la señal
    this.characters.update(chars => [...chars, newCharacter]);
  }
}

Initial State

Starts with only Goku in the characters list (power level over 9000!).

Character Interface

Both components use a TypeScript interface to define the character structure:
interface Character {
  id: number;
  name: string;
  power: number;
}
Properties:
  • id - Unique identifier for the character
  • name - Character’s name
  • power - Power level (numeric value)

Signal Properties

Form Signals

name = signal('');
power = signal(0);
These signals hold the form input values for creating new characters.

Characters Array Signal

characters = signal<Character[]>([
  { id: 1, name: 'Goku', power: 9001 },
  { id: 2, name: 'Vegeta', power: 8000 },
]);
A signal containing an array of characters. The generic type <Character[]> ensures type safety.
The signal contains the entire array. When updating, you must create a new array to maintain immutability and trigger reactivity.

Methods

addCharacter()

Adds a new character to the list after validation:
addCharacter() {
  // Validación correcta
  if (!this.name().trim() || this.power() <= 0) {
    return;
  }

  const newCharacter: Character = {
    id: this.characters().length + 1,
    name: this.name(),
    power: this.power(),
  };

  // Actualización correcta de la señal
  this.characters.update(chars => [...chars, newCharacter]);
}
Validation:
  • Name must not be empty or whitespace-only
  • Power must be greater than 0
Signal Update Pattern:
  • Uses update() with spread operator to create a new array
  • Maintains immutability by spreading existing characters: [...chars, newCharacter]
  • Generates sequential IDs based on array length
Always use the spread operator (...) or array methods like map() and filter() to create new arrays when updating signal arrays. Never mutate the array directly.

HTML Template

Both components share the same template structure:
dragonball-page.html
<h1>Dragonball Page</h1>
<hr />

<section class = "row">
    <div class="col-12 col-sm-6">
        <h3>Agregar: {{name()}}</h3>
        <input 
            type="text" 
            class="form-control" 
            placeholder="Nombre" 
            [value]="name()" 
            (change)="name.set(txtName.value)" 
            (input)="name.set(txtName.value)" 
            #txtName
        />


        <input 
            type="number" 
            class="form-control mt-2" 
            placeholder="Poder" 
            [value]="power()" 
            (input)="power.set(+txtPower.value)" 
            #txtPower
        />
        <button (click)="addCharacter()" class="mt-2 btn btn-primary">
            Agregar
        </button>
    </div>
    <div class="col-12 col-sm-6">
        <h3>Listado Fuertes:</h3>
        <ul>
            @for (character of characters(); track character.id; let idx = $index) {
                @if (character.power > 500){
                   <li>
                    <span>{{idx + 1}}-{{character.name}}</span>
                    <strong
                        [class.text-danger]="character.power > 9000"
                        [class.text-info]="character.power < 9000"> 
                        ({{character.power}})</strong>
                </li> 
                }@else {
                    <li><strong>Aquí no hay nadie fuerte</strong></li>
                }
                
            }
            
        </ul>
    </div>
</section>

Template Features

Two-Way Form Binding

<input 
    type="text" 
    [value]="name()" 
    (input)="name.set(txtName.value)" 
    #txtName
/>
Pattern:
  • [value] - Property binding to display signal value
  • (input) - Event binding to update signal on input
  • #txtName - Template reference variable to access input element

Number Input Conversion

<input 
    type="number" 
    [value]="power()" 
    (input)="power.set(+txtPower.value)" 
    #txtPower
/>
The + operator converts the string value to a number: +txtPower.value

New @for Syntax

@for (character of characters(); track character.id; let idx = $index) {
    <!-- content -->
}
Features:
  • track character.id - Tracks items by ID for efficient rendering
  • let idx = $index - Creates a variable with the current index
  • Loops through the signal array by invoking it: characters()

New @if/@else Syntax

@if (character.power > 500){
   <li><!-- strong character --></li>
}@else {
    <li><strong>Aquí no hay nadie fuerte</strong></li>
}
Angular’s new control flow syntax replaces *ngIf and *ngFor directives.

Dynamic CSS Classes

<strong
    [class.text-danger]="character.power > 9000"
    [class.text-info]="character.power < 9000"> 
    ({{character.power}})
</strong>
Conditional classes:
  • text-danger (red) - Applied if power > 9000
  • text-info (blue) - Applied if power < 9000
The famous “over 9000” reference is implemented as a conditional class that highlights exceptionally powerful characters in red.

Key Patterns

Immutable Array Updates

Always create a new array when updating signal arrays:
// ✅ Correct - Creates new array
this.characters.update(chars => [...chars, newCharacter]);

// ❌ Wrong - Mutates existing array
this.characters().push(newCharacter);

Form Validation

Validate input before processing:
if (!this.name().trim() || this.power() <= 0) {
  return;
}

Template Reference Variables

Use # to create references to DOM elements:
<input #txtName />
<button (click)="doSomething(txtName.value)">

Key Takeaways

  1. Signal arrays - Use signal<Type[]>() for array signals with type safety
  2. Immutable updates - Always create new arrays with spread operator
  3. New control flow - Use @for and @if instead of *ngFor and *ngIf
  4. Track by - Always provide track in @for for performance
  5. Form handling - Combine property binding and event binding for reactive forms
  6. Type conversion - Use + to convert string inputs to numbers
  7. Dynamic classes - Use [class.className] for conditional styling

Build docs developers (and LLMs) love