Explicación Signals
| Sitio: | Campus virtual DAW - damiansu |
| Curso: | Desarrollo Web en Entorno Cliente |
| Libro: | Explicación Signals |
| Imprimido por: | Invitado |
| Día: | sábado, 6 de diciembre de 2025, 04:50 |
1. Creación de proyecto
Creamos el proyecto base
ng new tema6-signals --standalone --skip-test --style=css --routing=false
Entramos en la carpeta del proyecto
cd tema6-signals
Lanzamos el ser servidor por el puerto por defecto 4200
ng serve -o2. Componentes
Crearemos varios componentes para ir mostrando su uso por separado
Componente de if
ng g component components/flujo-if --standalone
Componente for
ng g component components/flujo-for --standalone
Componente switch
ng g component components/flujo-switch --standalone
Directiva resaltar, para hacer una directiva personalizada y ver cómo modificar un elemento del DOM
ng g directive directivas/resaltar
Retocamos la directiva para que lo ponga en negrita y rojo
import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[resaltar]'
})
export class ResaltarDirective {
constructor(private el: ElementRef, private renderer: Renderer2) {
// Cambia el color del texto cuando se aplica la directiva
this.renderer.setStyle(this.el.nativeElement, 'color', 'red');
this.renderer.setStyle(this.el.nativeElement, 'font-weight', 'bold');
}
}3. Componente flujo-if
Explicación
Vamos a trabajar con el @if.
El objetivo es que la señal controla el valor y Angular controla la actualización del a interfaz
Ahora mismo tenemos creado
src/app/components/flujo-if/flujo-if.component.ts
src/app/components/flujo-if/flujo-if.component.html
src/app/components/flujo-if/flujo-if.component.css
Código actual del js:
import { Component } from '@angular/core';
@Component({
selector: 'app-flujo-if',
standalone: true,
templateUrl: './flujo-if.component.html',
styleUrl: './flujo-if.component.css',
})
export class FlujoIfComponent {}
Modificaciones:
En Angular usamos signals para manejar valores que cambian con el tiempo.
Una señal es como una “variable reactiva”: cada vez que cambia, Angular actualiza la vista automáticamente sin que tengamos que hacer nada más.
- Creamos una señal para guardar el valor, gracias a esto podremos:
- Asignar un valor por defecto (0)
- Leer el valor para saber cuál es: edad()
edad = signal<number>(0);
- Creamos un método que trabaja con esa señal
- Modificamos el valor con un método de signal: .set(nuevoValor)
actualizarEdad(valor: string) {
const numero = Number(valor);
this.edad.set(isNaN(numero) ? 0 : numero);
}
Código final
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-flujo-if',
standalone: true,
templateUrl: './flujo-if.component.html',
styleUrl: './flujo-if.component.css',
})
export class FlujoIfComponent {
// Signal que almacena la edad introducida
edad = signal<number>(0);
actualizarEdad(valor: string) {
const numero = Number(valor);
this.edad.set(isNaN(numero) ? 0 : numero);
}
}
Ahora en nuestra plantilla
Resultado final:
Primero creamos una señal para guardar un dato que queremos controlar, en este caso la edad.
Luego creamos un método que modifica esa señal.
Angular detecta automáticamente cuando la señal cambia, y actualiza el HTML sin necesidad de detectores ni suscripciones.
Por eso, el @if cambia solo cuando la señal cambia.
Código que incorporarmos en la plantilla html
src/app/components/flujo-if/flujo-if.component.html
Vamos a incluir un input, que cuando el usuario escribe en el campo numérico, se dispara el evento (input), llamará al método del componente actualizarEdad() y al pasarle la edad, la actualiza la señal con el valor nuevo
<input type="number" (input)="actualizarEdad($event.target.value)" />
@if (edad() < 18) {
<p>Menor de edad</p>
} @else {
<p>Mayor de edad</p>
}
4. Componente flujo-for
Explicación
- Uso de variables implícitas: $index, $first, $even, etc.
- Uso del bloque @empty
El objetivo va a ser con el @for recorrer colecciones y crear elementos en el DOM
Hay que recordar que una colección trabaja con:
|
Variable implícita |
Significado |
|---|---|
|
$index |
Índice del elemento |
|
$count |
Número total de elementos |
|
$first |
Es el primer elemento |
|
$last |
Es el último elemento |
|
$even |
Índice par |
|
$odd |
Índice impar |
Lo más fácil es asignarla nombres dentro del @for con let
@for (item of items; track item.id;
let i = $index, total = $count, primero = $first, ultimo = $last, par = $even, impar = $odd) {
<p>
{{ i }} / {{ total }}
@if (primero) {
<span>(inicio)</span>
}
@if (ultimo) {
<span>(fin)</span>
}
@if (par) {
<span>—par—</span>
}
</p>
}
Código final del ts
import { Component, signal } from '@angular/core';
interface Estudiante {
id: number;
nombre: string;
edad: number;
}
@Component({
selector: 'app-flujo-for',
standalone: true,
templateUrl: './flujo-for.component.html',
styleUrl: './flujo-for.component.css',
})
export class FlujoForComponent {
// Signal que contiene un array de estudiantes
estudiantes = signal<Estudiante[]>([
{ id: 1, nombre: 'Ana', edad: 20 },
{ id: 2, nombre: 'Luis', edad: 22 },
{ id: 3, nombre: 'Marta', edad: 19 },
]);
limpiarLista() {
this.estudiantes.set([]);
}
restaurarLista() {
this.estudiantes.set([
{ id: 1, nombre: 'Ana', edad: 20 },
{ id: 2, nombre: 'Luis', edad: 22 },
{ id: 3, nombre: 'Marta', edad: 19 },
]);
}
}
Código final de la plantilla
<h2>Ejemplo con @for</h2>
<button (click)="limpiarLista()">Vaciar lista</button>
<button (click)="restaurarLista()">Restaurar lista</button>
<ul>
@for (est of estudiantes(); track est.id;
let i = $index, primero = $first, par = $even) {
<li>
{{ i + 1 }}. {{ est.nombre }} ({{ est.edad }} años)
@if (primero) {
Primer elemento
}
@if (par) {
Fila par
}
</li>
} @empty {
<li>No hay estudiantes en la lista.</li>
}
</ul>
Incluimos un poco de css para ver resultado mejorado:
button {
margin-right: 10px;
}
li {
margin: 6px 0;
}
Y lo importamos en app.components.ts
import { FlujoForComponent } from './components/flujo-for/flujo-for.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [FlujoForComponent],
template: `
<app-flujo-for></app-flujo-for>
`
})
export class AppComponent {}5. Componente flujo-switch
Explicación
Código final del ts
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-flujo-switch',
standalone: true,
templateUrl: './flujo-switch.component.html',
styleUrl: './flujo-switch.component.css',
})
export class FlujoSwitchComponent {
// Lista de roles disponibles
roles = ['admin', 'editor', 'suscriptor', 'invitado'];
// Signal que almacena el rol seleccionado
rol = signal<string>('admin');
cambiarRol(valor: string) {
this.rol.set(valor);
}
}
Plantilla final del html
<h2>Ejemplo con @switch</h2>
<label>Selecciona un rol:</label>
<select (change)="cambiarRol($event.target.value)">
@for (r of roles; track r) {
<option [value]="r">{{ r }}</option>
}
</select>
<p>Rol actual: <strong>{{ rol() }}</strong></p>
<!-- Aquí entra el flujo @switch -->
@switch (rol()) {
@case ('admin') {
<p>Acceso completo al sistema.</p>
}
@case ('editor') {
<p>Puedes editar contenido, pero no administrar usuarios.</p>
}
@case ('suscriptor') {
<p>Puedes acceder al contenido pero no modificarlo.</p>
}
@default {
<p>Rol invitado: acceso limitado.</p>
}
}
Css para mejorar la presentación
label {
margin-right: 8px;
}
select {
margin-bottom: 10px;
}
p {
font-size: 1.05rem;
margin-top: 8px;
}
Importrarlo en app.components.ts
import { Component } from '@angular/core';
import { FlujoSwitchComponent } from './components/flujo-switch/flujo-switch.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [FlujoSwitchComponent],
template: `
<app-flujo-switch></app-flujo-switch>
`
})
export class AppComponent {}
O bien mostrar todos los flujos
import { Component } from '@angular/core';
import { FlujoIfComponent } from './components/flujo-if/flujo-if.component';
import { FlujoForComponent } from './components/flujo-for/flujo-for.component';
import { FlujoSwitchComponent } from './components/flujo-switch/flujo-switch.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [FlujoIfComponent, FlujoForComponent, FlujoSwitchComponent],
template: `
<app-flujo-if></app-flujo-if>
<hr />
<app-flujo-for></app-flujo-for>
<hr />
<app-flujo-switch></app-flujo-switch>
`
})
export class AppComponent {}6. Directiva destacar
Comando que hemos utlizado
ng g directive directivas/resaltar
Archivos que nos genera
src/app/directivas/resaltar.directive.ts
Código original
Modificaciones
Código final:
import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[resaltar]'
})
export class ResaltarDirective {
constructor(private el: ElementRef, private renderer: Renderer2) {
// Aplica estilo al elemento de forma segura
this.renderer.setStyle(this.el.nativeElement, 'color', 'red');
this.renderer.setStyle(this.el.nativeElement, 'font-weight', 'bold');
}
}
Su uso en html:
<p resaltar>Hola mundo</p>6.1. Directiva + Componente
Explicación
Una directiva personalizada nos permite crear un comportamiento reutilizable para cualquier elemento HTML.
Hemos creado la directiva resaltar, que aplica estilos al elemento donde se use.
Usamos Renderer2 porque Angular necesita una forma segura y multiplataforma de modificar el DOM.
Para utilizar la directiva en un componente standalone, debemos importarla en el array imports.
Finalmente, en la plantilla usamos la directiva como un atributo HTML:
<p resaltar>Texto</p>.
Trabajando
Partimos de flujo-if.components.ts
Uso de la directiva
import { Component, signal } from '@angular/core';
import { ResaltarDirective } from '../../directivas/resaltar.directive';
@Component({
selector: 'app-flujo-if',
standalone: true,
imports: [ResaltarDirective],
templateUrl: './flujo-if.component.html',
styleUrl: './flujo-if.component.css',
})
export class FlujoIfComponent {
edad = signal(0);
actualizarEdad(v: string) { this.edad.set(Number(v)); }
}
Ahora la usamos en la plantilla del componente flujo-if.component.html
<h2 resaltar>Ejemplo con @if + Directiva resaltar</h2>
<input type="number" (input)="actualizarEdad($event.target.value)" />
@if (edad() < 18) {
<p resaltar>Eres menor de edad.</p>
} @else {
<p>Eres mayor de edad.</p>
}
6.2. Directiva versión avanzada
Una versión más avanzada podría ser con color configurarle y efecto hover opcional (al pasar el ratón)
Código final:
import { Directive, ElementRef, Renderer2, Input, HostListener, OnInit } from '@angular/core';
@Directive({
selector: '[resaltar]'
})
export class ResaltarDirective implements OnInit {
// Color principal (por defecto, rojo)
@Input('resaltar') color: string = 'red';
// ¿Resaltar también al pasar el ratón?
@Input() resaltarHover: boolean = false;
private colorOriginal: string | null = null;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit(): void {
// Guardamos el color original para poder restaurarlo luego
this.colorOriginal = this.el.nativeElement.style.color;
// Aplicamos el estilo inicial
this.renderer.setStyle(this.el.nativeElement, 'color', this.color);
this.renderer.setStyle(this.el.nativeElement, 'font-weight', 'bold');
}
// Cuando el ratón entra
@HostListener('mouseenter')
onMouseEnter() {
if (this.resaltarHover) {
this.renderer.setStyle(this.el.nativeElement, 'color', this.color);
this.renderer.setStyle(this.el.nativeElement, 'text-decoration', 'underline');
}
}
// Cuando el ratón sale
@HostListener('mouseleave')
onMouseLeave() {
if (this.resaltarHover) {
this.renderer.setStyle(this.el.nativeElement, 'color', this.colorOriginal || '');
this.renderer.removeStyle(this.el.nativeElement, 'text-decoration');
}
}
}
La importamos en el componente (ya estaba)
import { Component, signal } from '@angular/core';
import { ResaltarDirective } from '../../directivas/resaltar.directive';
@Component({
selector: 'app-flujo-if',
standalone: true,
imports: [ResaltarDirective],
templateUrl: './flujo-if.component.html',
styleUrl: './flujo-if.component.css',
})
export class FlujoIfComponent {
edad = signal(0);
actualizarEdad(valor: string) {
this.edad.set(Number(valor) || 0);
}
}
La utilizamos en la plantilla
<h2 resaltar>Ejemplo @if + directiva resaltar (por defecto rojo)</h2>
<h3 [resaltar]="'blue'">Este título se resalta en azul</h3>
<p
[resaltar]="'green'"
[resaltarHover]="true">
Pasa el ratón por encima de este texto
</p>
<input type="number" (input)="actualizarEdad($event.target.value)" />
@if (edad() < 18) {
<p [resaltar]="'orange'">Eres menor de edad.</p>
} @else {
<p [resaltar]="'purple'" [resaltarHover]="true">
Eres mayor de edad.
</p>
}