linkedSignal para estado derivado

Avanzado
Angular
Angular
Actualizado: 25/09/2025

Fundamento teórico de linkedSignal

La función linkedSignal representa una evolución natural en el ecosistema de signals de Angular, diseñada para resolver una limitación fundamental que encontramos con los computed signals: la imposibilidad de modificar su valor directamente.

La necesidad de linkedSignal

Cuando trabajamos con computed signals, nos enfrentamos a una restricción importante: son de solo lectura. Un computed signal se recalcula automáticamente cuando cambian sus dependencias, pero no podemos asignarle un valor directamente. Esta característica, aunque útil para muchos casos, se convierte en un obstáculo cuando necesitamos:

  • Sincronización bidireccional entre diferentes fuentes de estado
  • Derivar valores que ocasionalmente necesitan ser sobrescritos manualmente
  • Combinar estado reactivo con actualizaciones imperativas
// Computed signal: solo lectura
const fullName = computed(() => `${firstName()} ${lastName()}`);
// fullName.set('John Smith'); // ❌ Error: no es posible

Qué es linkedSignal

linkedSignal es un primitivo reactivo que combina lo mejor de ambos mundos: la capacidad de derivarse automáticamente de otras signals (como computed) y la flexibilidad de ser modificado directamente (como un WritableSignal normal).

Esta función nos permite crear signals que:

  • Se actualizan automáticamente cuando cambian sus dependencias
  • Pueden ser modificados manualmente usando métodos como set() y update()
  • Mantienen sincronización compleja entre múltiples fuentes de estado
  • Proporcionan control granular sobre cuándo aplicar la derivación automática

Diferencias clave con computed

La principal diferencia conceptual entre computed y linkedSignal radica en el control de escritura:

| Característica | computed() | linkedSignal() | |----------------|------------|----------------| | Lectura reactiva | ✅ Sí | ✅ Sí | | Escritura directa | ❌ No | ✅ Sí | | Auto-derivación | ✅ Automática | ✅ Configurable | | Casos de uso | Valores calculados puros | Estado sincronizado complejo |

Casos de uso principales

Los linkedSignals brillan en escenarios donde necesitamos flexibilidad en la gestión de estado:

Sincronización de formularios: Cuando un campo puede calcularse automáticamente pero también permite entrada manual del usuario.

Cache inteligente: Estado que se deriva de fuentes remotas pero puede ser actualizado localmente para optimizar la experiencia de usuario.

Estado de UI complejo: Elementos de interfaz que responden tanto a cambios automáticos como a interacciones directas del usuario.

Configuración dinámica: Parámetros que tienen valores por defecto calculados pero pueden ser personalizados manualmente.

Modelo mental para linkedSignal

Para entender linkedSignal, es útil pensar en él como un signal que tiene dos "modos de operación":

  1. Modo derivado: Se comporta como un computed signal, actualizándose cuando cambian sus dependencias
  2. Modo manual: Se comporta como un WritableSignal normal, permitiendo asignaciones directas

La clave está en que linkedSignal nos da control sobre cuándo y cómo alternar entre estos modos, permitiendo sincronización sofisticada de estado que sería compleja de implementar con otros primitivos.

Esta flexibilidad hace de linkedSignal una herramienta indispensable para casos donde el estado necesita ser tanto reactivo como imperativo, proporcionando la base para patrones de sincronización avanzados en aplicaciones Angular modernas.

Creación de linkedSignal

La creación de linkedSignals en Angular sigue un patrón específico que combina la definición de la lógica de derivación con la configuración del comportamiento reactivo. Veamos cómo implementar estos primitivos avanzados en nuestros componentes.

Sintaxis básica

La función linkedSignal acepta una función de computación como primer parámetro, similar a computed, pero con la diferencia fundamental de que retorna un WritableSignal:

import { linkedSignal } from '@angular/core';

// Sintaxis básica
const myLinkedSignal = linkedSignal(() => {
  // Lógica de derivación
  return someValue;
});

A diferencia de computed, el linkedSignal nos permite posteriormente usar métodos como set() y update() para modificar su valor manualmente.

Parámetros de configuración

La función linkedSignal acepta un objeto de configuración como segundo parámetro opcional, que nos permite personalizar su comportamiento:

const linkedValue = linkedSignal(
  () => sourceSignal() * 2, // Función de computación
  {
    equal: (a, b) => a === b, // Función de comparación personalizada
  }
);

Ejemplo práctico: Campo de búsqueda inteligente

Creemos un linkedSignal que deriva automáticamente un término de búsqueda de la URL, pero permite sobrescritura manual:

@Component({
  selector: 'app-search',
  template: `
    <input 
      [value]="searchTerm()" 
      (input)="onSearchInput($event)"
      placeholder="Buscar productos...">
    
    @if (searchTerm()) {
      <p>Buscando: {{ searchTerm() }}</p>
    }
  `
})
export class SearchComponent {
  private route = inject(ActivatedRoute);
  
  // Signal que deriva de los query params de la ruta
  searchTerm = linkedSignal(() => {
    return this.route.snapshot.queryParams['q'] || '';
  });
  
  onSearchInput(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    // Podemos actualizar manualmente el linkedSignal
    this.searchTerm.set(value);
  }
}

Sincronización con múltiples fuentes

Los linkedSignals brillan cuando necesitamos derivar de múltiples signals pero mantener flexibilidad de escritura:

@Component({
  selector: 'app-user-profile',
  template: `
    <input 
      [value]="displayName()" 
      (input)="updateDisplayName($event)">
    
    <button (click)="resetToDefault()">
      Usar nombre automático
    </button>
  `
})
export class UserProfileComponent {
  firstName = signal('Juan');
  lastName = signal('Pérez');
  
  // LinkedSignal que deriva del nombre completo por defecto
  displayName = linkedSignal(() => {
    const first = this.firstName();
    const last = this.lastName();
    return `${first} ${last}`;
  });
  
  updateDisplayName(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.displayName.set(value);
  }
  
  resetToDefault() {
    // Forzamos recálculo desde las fuentes originales
    this.displayName.set(
      `${this.firstName()} ${this.lastName()}`
    );
  }
}

Configuración avanzada con equal

Podemos personalizar cuándo el linkedSignal considera que ha cambiado su valor:

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({...})
export class UserManagerComponent {
  users = signal<User[]>([]);
  selectedUserId = signal<number | null>(null);
  
  // LinkedSignal con comparación personalizada
  selectedUser = linkedSignal(
    () => {
      const id = this.selectedUserId();
      return this.users().find(user => user.id === id) || null;
    },
    {
      // Solo actualiza si cambia el ID del usuario
      equal: (a, b) => a?.id === b?.id
    }
  );
  
  selectUser(userId: number) {
    this.selectedUserId.set(userId);
  }
  
  // Permitir establecer directamente un usuario
  setCustomUser(user: User) {
    this.selectedUser.set(user);
  }
}

LinkedSignal con transformaciones

Podemos crear linkedSignals que aplican transformaciones complejas:

@Component({
  selector: 'app-price-calculator',
  template: `
    <input 
      type="number" 
      [value]="basePrice()" 
      (input)="updateBasePrice($event)">
    
    <select (change)="updateTax($event)">
      <option value="0.21">IVA 21%</option>
      <option value="0.10">IVA 10%</option>
      <option value="0.04">IVA 4%</option>
    </select>
    
    <input 
      type="number" 
      [value]="finalPrice()" 
      (input)="setFinalPrice($event)"
      placeholder="Precio final manual">
    
    <p>Precio base: {{ basePrice() }}€</p>
    <p>Precio final: {{ finalPrice() }}€</p>
  `
})
export class PriceCalculatorComponent {
  basePrice = signal(100);
  taxRate = signal(0.21);
  
  // LinkedSignal que calcula precio final con impuestos
  finalPrice = linkedSignal(() => {
    const base = this.basePrice();
    const tax = this.taxRate();
    return Number((base * (1 + tax)).toFixed(2));
  });
  
  updateBasePrice(event: Event) {
    const value = +(event.target as HTMLInputElement).value;
    this.basePrice.set(value);
  }
  
  updateTax(event: Event) {
    const value = +(event.target as HTMLSelectElement).value;
    this.taxRate.set(value);
  }
  
  setFinalPrice(event: Event) {
    const value = +(event.target as HTMLInputElement).value;
    // Override manual del precio calculado
    this.finalPrice.set(value);
  }
}

Gestión del estado de sincronización

Una práctica común es rastrear si el linkedSignal está en modo "manual" o "automático":

@Component({
  selector: 'app-smart-field',
  template: `
    <div class="field-container">
      <input 
        [value]="computedValue()" 
        (input)="handleManualInput($event)">
      
      @if (isManualOverride()) {
        <button (click)="resetToAuto()">
          🔄 Volver a automático
        </button>
      }
    </div>
  `
})
export class SmartFieldComponent {
  sourceValue = signal('valor inicial');
  isManualOverride = signal(false);
  
  computedValue = linkedSignal(() => {
    // Solo aplica transformación automática si no hay override
    if (!this.isManualOverride()) {
      return this.sourceValue().toUpperCase();
    }
    return this.computedValue(); // Mantiene valor actual
  });
  
  handleManualInput(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.computedValue.set(value);
    this.isManualOverride.set(true);
  }
  
  resetToAuto() {
    this.isManualOverride.set(false);
    // Forzar recálculo automático
    this.computedValue.set(this.sourceValue().toUpperCase());
  }
}

La creación de linkedSignals nos proporciona una herramienta flexible para manejar estado que necesita ser tanto reactivo como modificable directamente, permitiendo implementar patrones de sincronización sofisticados que serían complejos con otros primitivos de Angular.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la limitación de los computed signals y la necesidad de linkedSignal.
  • Aprender la sintaxis y configuración básica de linkedSignal.
  • Identificar casos de uso donde linkedSignal es más adecuado que computed.
  • Implementar linkedSignals que combinan derivación automática y modificaciones manuales.
  • Gestionar estados complejos y sincronización bidireccional con linkedSignal.