---
title: "How to Share Data Between Components in Angular (Signals & Services)"
date: "2022-02-14"
slug: "how-to-share-data-between-components-in-angular"
author: "Dany Paredes"
canonical: "https://danywalls.com/how-to-share-data-between-components-in-angular"
description: "Learn the best patterns to share data between Angular components. Compare legacy Input/Output decorators and BehaviorSubjects with modern Signals, input(), output(), model(), and Signal-based services."
---


When building applications in Angular, sharing data between components is a fundamental requirement. Whether you need to pass an item down to a child card, notify a parent when a user clicks a button, or sync shopping cart state across unrelated header and sidebar components, Angular provides several distinct communication patterns.

> ⚡ **Using Modern Angular (v17+)?** Jump directly to [Modern Angular: Signals `input()`, `output()`, `model()`, and Signal Services](#modern-angular-signals-input-output-model-and-signal-services) to see the idiomatic, zero-leak approach.

In this guide, we will compare the classic patterns (`@Input`, `@Output`, `BehaviorSubject`) with the modern Angular primitives (`input()`, `output()`, `model()`, and Signal Services).

Let's start with direct Parent-to-Child and Child-to-Parent communication.

---

## 1. Parent-to-Child & Child-to-Parent Communication 🔄

When two components have a direct hierarchical relationship, passing data down and emitting events up is the cleanest approach.

### Legacy Angular (v2 – v16): `@Input()` & `@Output()` Decorators

In older versions of Angular, we used decorators and `EventEmitter`:

```typescript
// child.component.ts (Legacy)
import { Component, Input, Output, EventEmitter } from '@angular/core';

export interface Product {
  id: number;
  name: string;
  price: number;
}

@Component({
  selector: 'app-product-card',
  template: `
    <div class="product-card">
      <h3>{{ product?.name }}</h3>
      <p>\${{ product?.price }}</p>
      <button (click)="selectProduct()">Select</button>
    </div>
  `
})
export class ProductCardComponent {
  @Input() product?: Product;
  @Output() selected = new EventEmitter<Product>();

  selectProduct() {
    if (this.product) {
      this.selected.emit(this.product);
    }
  }
}
```

Usage in the parent component template:

```html
<app-product-card
  [product]="currentProduct"
  (selected)="onProductSelected($event)"
/>
```

Now let's see how modern Angular simplifies this with Signals.

---

## 2. Modern Angular (v17+): `input()`, `output()`, and `model()` ⚡

Modern Angular replaces property decorators with reactive Signal functions.

### Modern Child Component with Signal Inputs and Outputs

```typescript
// child.component.ts (Modern Standalone)
import { Component, input, output } from '@angular/core';

export interface Product {
  id: number;
  name: string;
  price: number;
}

@Component({
  selector: 'app-product-card',
  standalone: true,
  template: `
    <div class="product-card">
      <h3>{{ product().name }}</h3>
      <p>\${{ product().price }}</p>
      <button (click)="selectProduct()">Select</button>
    </div>
  `
})
export class ProductCardComponent {
  // 1. Required signal input (type-safe at compile time!)
  readonly product = input.required<Product>();

  // 2. Output function (no EventEmitter instantiation boilerplate)
  readonly selected = output<Product>();

  selectProduct() {
    this.selected.emit(this.product());
  }
}
```

### Two-Way Data Binding with `model()` (Angular 17.2+)

If you need two-way binding between parent and child (e.g. an expanded panel state or quantity counter), use `model()`:

```typescript
// quantity-picker.component.ts
import { Component, model } from '@angular/core';

@Component({
  selector: 'app-quantity-picker',
  standalone: true,
  template: `
    <div class="flex gap-2">
      <button (click)="quantity.set(quantity() - 1)">-</button>
      <span>{{ quantity() }}</span>
      <button (click)="quantity.set(quantity() + 1)">+</button>
    </div>
  `
})
export class QuantityPickerComponent {
  // Writable two-way signal!
  readonly quantity = model(1);
}
```

Parent template usage:
```html
<!-- Automatically synchronizes both ways! -->
<app-quantity-picker [(quantity)]="itemCount" />
```

Now, what if components are siblings or not directly connected in the template hierarchy? Let's explore Shared Services.

---

## 3. Cross-Component Communication with Services 🌐

When components live in separate branches of your application or across router outlets, passing inputs through multiple layers (prop drilling) becomes unmaintainable. An Injectable Service solves this cleanly.

### Approach A: Legacy `BehaviorSubject` Service (RxJS)

```typescript
// cart.service.ts (Legacy RxJS)
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class CartService {
  private readonly items$ = new BehaviorSubject<Product[]>([]);
  readonly currentItems$ = this.items$.asObservable();

  addItem(product: Product) {
    const updated = [...this.items$.value, product];
    this.items$.next(updated);
  }
}
```

In components, you had to manage subscriptions with `async` pipe or manually unsubscribe to prevent memory leaks:

```html
<header>Cart Items: {{ (cartService.currentItems$ | async)?.length }}</header>
```

### Approach B: Modern Signal-Based State Service (Zero Leaks, No Async Pipe)

In modern Angular, Signal Services provide synchronous, readable state with zero subscription management:

```typescript
// cart.service.ts (Modern Signal State)
import { Injectable, signal, computed } from '@angular/core';
import { Product } from './product.model';

@Injectable({ providedIn: 'root' })
export class CartService {
  // 1. Private writable signal
  private readonly itemsSignal = signal<Product[]>([]);

  // 2. Public read-only signal
  readonly items = this.itemsSignal.asReadonly();

  // 3. Computed derived state (auto-recalculates when items change!)
  readonly totalCount = computed(() => this.items().length);
  readonly totalPrice = computed(() =>
    this.items().reduce((acc, item) => acc + item.price, 0)
  );

  addItem(product: Product) {
    this.itemsSignal.update((current) => [...current, product]);
  }

  removeItem(productId: number) {
    this.itemsSignal.update((current) => current.filter((p) => p.id !== productId));
  }
}
```

### Consuming the Signal Service in Any Component

```typescript
// cart-badge.component.ts
import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';

@Component({
  selector: 'app-cart-badge',
  standalone: true,
  template: `
    <div class="badge">
      🛒 {{ cart.totalCount() }} items (\${{ cart.totalPrice() }})
    </div>
  `
})
export class CartBadgeComponent {
  readonly cart = inject(CartService);
}
```

---

## 4. Querying Direct Child Components with `viewChild()` 🔍

When a parent component needs direct access to a child component's public methods or properties:

```typescript
import { Component, viewChild, AfterViewInit } from '@angular/core';
import { ProductCardComponent } from './product-card.component';

@Component({
  selector: 'app-product-list',
  standalone: true,
  imports: [ProductCardComponent],
  template: `<app-product-card #card [product]="featuredProduct" />`
})
export class ProductListComponent {
  // Modern Signal-based viewChild (replaces @ViewChild decorator)
  readonly card = viewChild.required<ProductCardComponent>('card');

  focusCard() {
    console.log('Accessing child component instance:', this.card());
  }
}
```

---

## Communication Matrix: When to Use Which Pattern 📊

| Scenario | Recommended Pattern | Modern Angular Primitive |
| :--- | :--- | :--- |
| **Parent to Child** | One-way data flow | `input()` or `input.required()` |
| **Child to Parent** | Custom event notification | `output()` |
| **Two-way synchronization** | Mutual state sync | `model()` |
| **Unrelated / Sibling Components** | Shared singleton service | `signal()` + `computed()` service |
| **Parent accessing Child methods** | Direct component reference | `viewChild()` signal |

---

## Recap 🛠️

To write clean, maintainable Angular applications:
1. Use **`input()`** and **`output()`** for standard parent-child communication.
2. Use **`model()`** for two-way state synchronization without paired `@Input/@Output` ceremony.
3. Use **Signal Services (`signal()`, `asReadonly()`, `computed()`)** to share global and cross-component state without memory leaks.

For more modern Angular architectural patterns, check out my articles on [How to Simplify Component Communication with model() and Signals](/how-to-simplify-angular-component-communication-with-model-and-signals) and [How to Use ng-template, ng-container, and ng-content](/how-to-get-and-use-ng-template-ng-container-and-ng-content)!

