πŸ“Œ Share Data Between Components in Angular: Best Practices & Different Approaches



This content originally appeared on DEV Community and was authored by Rohit Singh

When building Angular applications, one common challenge developers face is sharing data between components. Sometimes you need to pass data from parent to child, sometimes from child to parent, and in many cases between unrelated components.

In this article, we’ll explore different approaches to share data in Angular, along with best practices to keep your code clean and maintainable.

🔹 1. Input & Output Properties (Parent ↔ Child Communication)

The most common way to share data is through @Input() and @Output() decorators.

👉 Use @Input() to pass data from parent to child.
👉 Use @Output() with EventEmitter to send data from child to parent.

Example:

// child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <p>Received from parent: {{ message }}</p>
    <button (click)="sendBack()">Send to Parent</button>
  `
})
export class ChildComponent {
  @Input() message!: string;
  @Output() reply = new EventEmitter<string>();

  sendBack() {
    this.reply.emit('Hello Parent!');
  }
}

// parent.component.ts
@Component({
  selector: 'app-parent',
  template: `
    <app-child 
      [message]="'Hello Child!'" 
      (reply)="onReply($event)">
    </app-child>
  `
})
export class ParentComponent {
  onReply(data: string) {
    console.log('Child said:', data);
  }
}

✅ Best for: Simple parent-child communication.
❌ Not good for: Sibling or unrelated components.

🔹 2. ViewChild / ContentChild (Access Child Component Methods)

Sometimes you don’t just need data, but want to call a method on the child component directly. Angular provides @ViewChild for this.

// child.component.ts
@Component({
  selector: 'app-child',
  template: `<p>{{ counter }}</p>`
})
export class ChildComponent {
  counter = 0;
  increment() {
    this.counter++;
  }
}

// parent.component.ts
@Component({
  selector: 'app-parent',
  template: `
    <app-child></app-child>
    <button (click)="increaseChild()">Increase Counter</button>
  `
})
export class ParentComponent {
  @ViewChild(ChildComponent) child!: ChildComponent;

  increaseChild() {
    this.child.increment();
  }
}

✅ Best for: Direct method access in parent.
❌ Avoid: Overusing, as it creates tight coupling.

🔹 3. Shared Service with RxJS (Recommended for Siblings & Unrelated Components)

The best practice for sharing data between unrelated or sibling components is using a shared service with RxJS Subjects/BehaviorSubjects.

// shared.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class SharedService {
  private dataSource = new BehaviorSubject<string>('Initial Data');
  data$ = this.dataSource.asObservable();

  updateData(newData: string) {
    this.dataSource.next(newData);
  }
}

// sender.component.ts
@Component({
  selector: 'app-sender',
  template: `<button (click)="sendData()">Send Data</button>`
})
export class SenderComponent {
  constructor(private shared: SharedService) {}
  sendData() {
    this.shared.updateData('Hello from Sender!');
  }
}

// receiver.component.ts
@Component({
  selector: 'app-receiver',
  template: `<p>Received: {{ message }}</p>`
})
export class ReceiverComponent {
  message = '';
  constructor(private shared: SharedService) {
    this.shared.data$.subscribe(data => this.message = data);
  }
}

✅ Best for: Sibling or unrelated components.
✅ Scalable & testable.
❌ Be careful: Always unsubscribe when needed to prevent memory leaks.

🔹 4. State Management Libraries (NgRx, Akita, NGXS)

For large-scale applications, managing data with services may become complex. This is where state management libraries like NgRx come into play.

NgRx uses Redux-style global state with Actions, Reducers, and Effects.

All components subscribe to a centralized store for consistent data.

✅ Best for: Enterprise applications with complex state.
❌ Overkill for: Small to medium projects.

🔹 5. Router Parameters / Query Params

Sometimes, the best way to share data between components is via route parameters.

// sender.component.ts
this.router.navigate(['/details', { id: 123 }]);

// receiver.component.ts
this.route.params.subscribe(params => {
  console.log(params['id']); // 123
});

✅ Best for: Passing IDs, filters, or state via navigation.
❌ Not for: Continuous data updates.

🚀 Best Practices

Use Input/Output for parent-child communication.

Use Shared Service with RxJS for sibling/unrelated components.

Use State Management (NgRx) for large apps.

Use Router Params for navigation-based data sharing.

Avoid tight coupling with ViewChild unless necessary.

Always unsubscribe from observables (takeUntil, async pipe).

🎯 Conclusion

Angular offers multiple ways to share data between components. The right choice depends on app size, component relation, and scalability needs.

Small apps: @Input, @Output, Shared Service

Medium apps: Shared Service with RxJS

Large apps: NgRx or NGXS

👉 Pick the simplest solution that fits your use case.

🚀 Rohit Singh 🚀 – Medium

Read writing from 🚀 Rohit Singh 🚀 on Medium. Full-stack developer with 6+ years in Angular, Node.js & AWS. Sharing tips, best practices & real-world lessons from building scalable apps.

favicon medium.com


This content originally appeared on DEV Community and was authored by Rohit Singh