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 -o

2. 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>
}