Comunicación entre componentes Angular

Intermedio
Angular
Angular
Actualizado: 04/05/2026
flowchart TD
    Parent[Componente padre] -->|input signal / Input| Child[Componente hijo]
    Child -->|output signal / EventEmitter| Parent
    Parent -->|model two-way| Child
    Parent -->|"@ViewChild / viewChild signal"| Child
    Parent -->|"<ng-content>"| Child
    Child -.no relacionados.-> Service["Servicio compartido<br/>Subject / Signal"]
    Sibling1[Componente hermano 1] -->|emit| Service
    Service -->|listen| Sibling2[Componente hermano 2]
    Service --> Comp3[Componente lejano]
    Service -.NgRx / SignalStore.-> Store["(&quot;Store global<br/>state slice&quot;)"]
    Store --> Comp3
    style Parent fill:#fff3e0,stroke:#ef6c00
    style Child fill:#e3f2fd,stroke:#1565c0
    style Service fill:#e8f5e9,stroke:#2e7d32
    style Store fill:#fce4ec,stroke:#ad1457

La comunicación entre componentes es esencial en las aplicaciones Angular para mantener la coherencia y la actualización de datos entre diferentes partes de la aplicación.

En Angular, existen varias técnicas para lograr esta comunicación entre componentes, y se pueden dividir en dos categorías principales: comunicación entre componentes relacionados y comunicación entre componentes no relacionados.

Comunicación entre componentes relacionados

Comunicación mediante inputs (input())

Los componentes pueden intercambiar información pasando datos a través de inputs, también conocido como "binding" en Angular.

Un componente puede recibir datos de otro componente utilizando la función input() basada en signals (enfoque recomendado en Angular 21) o el decorador clásico @Input().

Por ejemplo, supongamos que hay un componente UsuarioComponent que muestra detalles de un usuario:

// En UsuarioComponent - enfoque moderno con input()
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-usuario',
  template: `
    @if (usuario()) {
      <div>
        <h2>{{ usuario()!.nombre }}</h2>
        <p>{{ usuario()!.descripcion }}</p>
      </div>
    }
  `
})
export class UsuarioComponent {
  usuario = input<Usuario>();
}

Para usar este componente en otro componente, simplemente se enlaza la propiedad usuario:

@Component({
  selector: 'app-contenedor',
  imports: [UsuarioComponent],
  template: `
    <app-usuario [usuario]="usuarioActual" />
  `
})
export class ContenedorComponent {
  usuarioActual: Usuario = { nombre: 'Ana', descripcion: 'Desarrolladora' };
}

Comunicación mediante eventos (output())

Los componentes pueden comunicarse enviando eventos utilizando la función output() (enfoque recomendado en Angular 21) o el decorador clásico @Output() con EventEmitter.

Por ejemplo, consideremos un componente ContadorComponent que permite al usuario aumentar un contador y notificar a otros componentes cuando cambia:

import { Component, output } from '@angular/core';

@Component({
  selector: 'app-contador',
  template: `
    <button (click)="aumentarContador()">Aumentar</button>
  `
})
export class ContadorComponent {
  contador = 0;
  cambioContador = output<number>();

  aumentarContador() {
    this.contador++;
    this.cambioContador.emit(this.contador);
  }
}

En el componente que utiliza ContadorComponent:

@Component({
  selector: 'app-contenedor',
  imports: [ContadorComponent],
  template: `
    <app-contador (cambioContador)="actualizarContador($event)" />
    <p>Contador actual: {{ contadorActual }}</p>
  `
})
export class ContenedorComponent {
  contadorActual = 0;

  actualizarContador(nuevoValor: number) {
    this.contadorActual = nuevoValor;
  }
}

Proyección de contenido

La proyección de contenido utiliza la etiqueta <ng-content> para insertar contenido dinámico en un componente desde su componente padre.

@Component({
  selector: 'app-mi-componente',
  template: `
    <div class="contenedor">
      <ng-content select="[header]"></ng-content>
      <ng-content select="[content]"></ng-content>
    </div>
  `
})
export class MiComponenteComponent {}

Uso en el componente padre:

@Component({
  selector: 'app-contenedor',
  imports: [MiComponenteComponent],
  template: `
    <app-mi-componente>
      <h3 header>Título personalizado</h3>
      <p content>Contenido personalizado</p>
    </app-mi-componente>
  `
})
export class ContenedorComponent {}

Comunicación entre componentes no relacionados

Servicios compartidos

Los servicios son instancias compartidas que permiten la comunicación entre componentes que no están directamente relacionados. 

La inyección de dependencias es el mecanismo mediante el cual se proporcionan estas instancias a los componentes que las necesitan.

Un servicio puede tener métodos para almacenar y recuperar datos, lo que facilita el intercambio de información entre componentes.

Por ejemplo, un UsuarioService podría proporcionar información de usuario a diferentes partes de la aplicación:

// En UsuarioService
import { Injectable, signal, computed } from '@angular/core';
import { Usuario } from './usuario.model';

@Injectable({
  providedIn: 'root'
})
export class UsuarioService {
 
 usuarioActual: Usuario; // Suponiendo que Usuario es una interfaz o clase

 constructor() {
     this.usuarioActual = {
         id: 1,
         nombre: 'Usuario Ejemplo',
         correo: 'usuario@example.com'
     };
 }

 actualizarUsuario(usuarioNuevo: Usuario) {
 this.usuarioActual = usuarioNuevo;
 }

 obtenerUsuario(): Usuario {
 return this.usuarioActual;
 }
}

Mediante la inyección de dependencias, un componente puede acceder al servicio usando la función inject():

// En cualquier componente
import { Component, inject } from '@angular/core';
import { UsuarioService } from './usuario.service';

@Component({
 selector: 'app-mi-componente',
 template: `
   <p>{{ usuario.nombre }}</p>
 `
})
export class MiComponenteComponent {
 private usuarioService = inject(UsuarioService);
 usuario = this.usuarioService.obtenerUsuario();
}

Caso B2B: arquitectura de componentes en backoffices y portales

En entidades bancarias con backoffices Angular, la comunicación padre-hijo vía input() y output() modela típicamente los formularios maestro-detalle: una lista de cuentas (account-list) emite el evento de selección al contenedor, que pasa la cuenta seleccionada como input al panel de detalle (account-detail). Vuestro equipo gana porque cada componente queda autónomo y testeable de forma aislada con @angular/cdk/testing o Cypress Component Testing.

En administraciones públicas con sedes electrónicas, los formularios de tramitación combinan componentes hijos por paso del wizard (datos personales, datos del expediente, documentación, firma) y un componente padre que orquesta el flujo. La comunicación con output() permite al padre coordinar la transición entre pasos sin acoplar los hijos entre sí.

En telco con portales de gestión de líneas, los servicios compartidos (Injectable({ providedIn: 'root' })) con signal mantienen el estado del cliente seleccionado disponible para todas las pantallas. Cuando el usuario cambia el cliente activo, todos los componentes suscritos al signal se actualizan reactivamente sin emitir eventos manualmente. La organización gana en simplicidad de código y mantenibilidad.

En retail con dashboards de operaciones, los servicios con Subject<T> o BehaviorSubject<T> notifican eventos como "nuevo pedido recibido" a todas las pantallas que lo escuchan, sin acoplar emisor y receptor. Para arquitecturas más grandes (decenas de componentes), NgRx Signal Store o NgRx clásico estructuran el estado global con acciones, selectores y efectos.

Versiones (2025)

Angular 19 (noviembre 2024) consolida signal(), input(), output(), model() y viewChild() como API canónica para componentes signal-based. La función input() reemplaza al decorador @Input() y devuelve un InputSignal<T>. output() reemplaza a @Output() EventEmitter. model() (Angular 17.2+) crea two-way binding signal-based. viewChild() reemplaza al decorador @ViewChild. La compatibilidad con la API basada en decoradores se mantiene durante la transición.

Para gestión de estado, NgRx 18+ soporta SignalStore (estado basado en signals con menos boilerplate que el Store clásico). TanStack Query Angular (Angular adapter, 2024) es una alternativa moderna para sincronización con backend.

Anti-patrones y pitfalls

Mezclar @Input() clásico con input() signal en el mismo componente. Funciona pero confunde al equipo. Migrad de forma consistente.

Modificar el input desde el hijo. input() es de solo lectura: el hijo no puede asignar this.usuario.set(...). Para modificación bidireccional, usad model().

Subjects no completados al destruir el componente. Si un servicio expone un Subject y los componentes se suscriben sin takeUntilDestroyed(), las suscripciones quedan vivas tras destruir el componente y provocan memory leaks. Angular 16+ ofrece takeUntilDestroyed() que se suscribe al ciclo de vida OnDestroy.

Servicios providedIn: 'any' o por componente sin entender el alcance. 'root' crea singleton; 'any' crea instancia por lazy module; provider en el componente crea instancia nueva por instancia. Confundir esto provoca estados inconsistentes.

EventEmitter para flujos múltiples a observers desconocidos. EventEmitter es para emitir hacia el padre Angular; no es un Subject general. Para comunicación many-to-many, usad un Subject<T> en un servicio.

Sobrecargar signal con objetos profundos sin update. Si vuestro signal contiene un objeto y mutáis una propiedad sin reasignar (signal.update(s => ({...s, prop: nuevo}))), Angular no detecta el cambio. Usad siempre inmutabilidad o mutate() (deprecado en Angular 19).

Proyección de contenido sin selectores. <ng-content> sin select proyecta todo el contenido al primer slot. Para componentes con varios slots, usad selectores explícitos.

Comparativa con alternativas

Frente a React (props y state), Angular ofrece la misma capacidad con input() y output(). La diferencia es que Angular tiene DI integrada, lo que simplifica la inyección de servicios sin librerías adicionales.

Frente a Vue 3 (props, emits, provide/inject), Angular es más estructurado: TypeScript estricto por defecto, compilador AOT y modelo de cambio detection más predecible.

Frente a Akita o NgRx clásico, NgRx Signal Store reduce el boilerplate y aprovecha signals nativos. Para nuevos proyectos B2B en 2025, SignalStore es la opción recomendada.

Frente a RxJS Subjects, los signals son más simples mentalmente pero menos potentes para flujos asíncronos complejos. Combinad ambos: signals para estado, RxJS para flujos asíncronos (HTTP, WebSocket, debounce).

Documentación oficial

Las referencias son angular.dev/guide/components/inputs, angular.dev/guide/components/outputs, angular.dev/guide/signals, angular.dev/guide/components/queries (para viewChild). Para servicios, angular.dev/guide/di. Para gestión de estado avanzada, la documentación oficial de NgRx (ngrx.io) cubre Signal Store y Store clásico.

La comunicación entre componentes es la base de cualquier aplicación Angular no trivial. Para vuestro equipo en proyectos B2B, dominar input(), output(), servicios con signals y, cuando aplique, NgRx Signal Store es la línea base para arquitecturas mantenibles a varios años vista.

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 importancia de la comunicación entre componentes en Angular.
  • Distinguir entre comunicación entre componentes relacionados y no relacionados.
  • Dominar el uso de propiedades y eventos para comunicación entre componentes.
  • Aprender a utilizar servicios compartidos para comunicación entre componentes.