CodeForge

Angular 21 de Cero a Experto / Signals a fondo

linkedSignal y resource(): estado derivado y datos async

Teoría22 min20 XP

Cierras la teoría de señales con dos herramientas que resuelven problemas del mundo real. linkedSignal es para cuando quieres un valor que normalmente se DERIVA de otro, pero que el usuario también puede sobrescribir (y que se "resetea" cuando la fuente cambia). resource() es la joya para datos asíncronos: cargar de una API con estados de carga, error y valor —todos como señales— de forma declarativa. Hoy las conoces y sabes cuándo usar cada una.

linkedSignal: derivado pero escribible

Un computed es de solo lectura: no puedes escribirle. Pero a veces quieres un valor que POR DEFECTO se deriva de otra señal, y que el usuario pueda cambiar temporalmente —hasta que la fuente cambie y lo resetee—.

linked.component.ts
import { Component, signal, linkedSignal } from '@angular/core';

@Component({ selector: 'app-form-gasto', template: `...` })
export class FormGastoComponent {
comercio = signal<'super' | 'gasolinera' | 'cine'>('super');

// categoría: derivada del comercio, pero el usuario puede sobrescribirla
categoria = linkedSignal(() => {
  switch (this.comercio()) {
    case 'super':      return 'comida';
    case 'gasolinera': return 'transporte';
    case 'cine':       return 'ocio';
  }
});

// el usuario la cambia a mano:  this.categoria.set('otro')
// pero si cambia 'comercio', se recalcula desde la fuente (se resetea)
}

La diferencia con computed: computed NUNCA se puede escribir (siempre refleja el cálculo); linkedSignal se puede escribir (set/update), pero vuelve a derivarse cuando su fuente cambia. Es el punto medio entre "señal libre" y "señal derivada".

resource(): datos asíncronos como señales

Cargar datos de una API tiene siempre los mismos tres estados: cargando, error, y datos. resource() los empaqueta en señales y vuelve a cargar automáticamente cuando cambia un parámetro de entrada:

detalle.component.ts
import { Component, input, resource } from '@angular/core';

@Component({
selector: 'app-detalle-gasto',
template: `
  @if (gastoRes.isLoading()) {
    <p>Cargando…</p>
  } @else if (gastoRes.error()) {
    <p>Error al cargar el gasto.</p>
  } @else {
    <h3>{{ gastoRes.value()?.nombre }}</h3>
  }
`,
})
export class DetalleGastoComponent {
id = input.required<number>();

gastoRes = resource({
  params: () => ({ id: this.id() }),          // cuando 'id' cambia, recarga
  loader: async ({ params }) => {
    const res = await fetch(`/api/gastos/${params.id}`);
    if (!res.ok) throw new Error('Falló la carga');
    return res.json();
  },
});
}

resource() te da señales listas para el template:

  • isLoading()true mientras carga.
  • error() — el error si el loader lanzó.
  • value() — los datos cuando llegan.
  • reload() — método para recargar a mano.

Y lo mejor: si id (una señal en params) cambia, resource vuelve a ejecutar el loader solo. Es reactividad aplicada a datos remotos. Si esto te recuerda a useQuery de TanStack en React, vas bien encaminado: mismo problema, solución integrada en Angular.

Antes (subscribe manual) vs Ahora (resource)

¿Cuándo usarías `linkedSignal` en lugar de `computed`?

Mini-reto

Sin código ejecutable (estas APIs viven en Angular): 1) describe un caso de tu app "Mi Bolsillo" donde linkedSignal encaje mejor que computed —piensa en un valor autorrellenado que el usuario pueda corregir—; 2) escribe el esqueleto de un resource() que cargue la lista de gastos de un mes, con params dependiendo de una señal mes; 3) enumera las tres señales que expondrías en el template para cargando/error/datos.

Qué sigue

Cierras la teoría de señales con las siete herramientas: signal, computed, effect, signal inputs, model, linkedSignal y resource. Es hora del reto que corona el módulo: vas a construir tu PROPIO sistema de señales completo —signal, computed y effect con rastreo de dependencias real— desde cero, en vivo. Entenderás Angular por dentro como pocos.