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
DragonballSuperPage
DragonballPage Component
Located at src/app/pages/dragonball/TypeScript Implementation
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!).DragonballSuperPage Component
Located at src/app/pages/dragonball-super/TypeScript Implementation
import { Component, signal } from '@angular/core';
interface Character {
id: number;
name: string;
power: number;
}
@Component({
templateUrl: './dragonball-super-page.html',
})
export class DragonballSuperPage {
name = signal('');
power = signal(0);
characters = signal<Character[]>([
{ id: 1, name: 'Goku', power: 9001 },
{ id: 2, name: 'Vegeta', power: 8000 },
]);
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 both Goku and Vegeta in the characters list.
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
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:
<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
<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
<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);
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
- Signal arrays - Use
signal<Type[]>() for array signals with type safety
- Immutable updates - Always create new arrays with spread operator
- New control flow - Use
@for and @if instead of *ngFor and *ngIf
- Track by - Always provide
track in @for for performance
- Form handling - Combine property binding and event binding for reactive forms
- Type conversion - Use
+ to convert string inputs to numbers
- Dynamic classes - Use
[class.className] for conditional styling