Sharing data between components

Sharing data between components

In Angular, communication between components plays a key role in building dynamic and interactive applications. Proper communication between the components ensures an efficient and smooth exchange of data and events. There are several ways to establish communication between components in Angular, each suited to specific scenarios.

Let's explore the different component communication techniques.

1) Custom property binding and event binding

  • Sharing Data from Parent to Child Component

    The @Input decorator allows you to pass data from a parent component to a child component. By binding a property with the @Input decorator in the child component, you can receive data from the parent component.

    Let's look into a simple code example.

    Parent component

      import { Component } from '@angular/core';
    
      @Component({
        selector: 'app-parent',
        template: `
          <app-child [data]="parentMessage"></app-child>
        `
      })
      export class ParentComponent {
        parentMessage: string = "Hello! Message from parent.";
      }
    

    Child component

      import { Component, Input } from '@angular/core';
    
      @Component({
        selector: 'app-child',
        template: `
          <p>{{ message }}</p>
        `
      })
      export class ChildComponent {
        @Input() message: string;
      }
    
  • Sharing data from child to parent component

    The @Output decorator is used to emit data out of the child component to a parent component that listens.

    Parent Component

      import { Component } from '@angular/core';
    
      @Component({
        selector: 'app-parent',
        template: `
          <app-child (message)="onMessageReceived($event)"></app-child>
          <p>{{ data }}</p>
        `
      })
      export class ParentComponent {
        data: string;
    
        onMessageReceived(data: string) {
          this.data= data;
        }
      }
    

    Child component

      import { Component, Output, EventEmitter } from '@angular/core';
    
      @Component({
        selector: 'app-child',
        template: `
          <button (click)="onClick()">Send Message</button>
        `
      })
      export class ChildComponent {
        @Output() message = new EventEmitter<string>();
    
        onClick() {
          this.message.emit("Hello! Message from child.");
        }
      }
    

2)Shared Services

  • Shared Service Communication

    Shared services provide a centralized and efficient way to share data and states between different components. They facilitate communication between unrelated components and encourage code reuse and separation of concerns.

    Let's create methods that set and get the shared data in the shared service.

    
      import { Injectable } from '@angular/core';
    
      @Injectable({
        providedIn: 'root'
      })
      export class SharedService {
        sharedData: string = 'data';
    
        setSharedData(data: string) {
          this.sharedData = data;
        }
    
        getSharedData() {
          return this.sharedData;
        }
      }
    

    Now let's have two components where one component updates the shared data in the shared service, and the other component reads and displays the updated data.

    1st component

      import { Component } from '@angular/core';
      import { SharedService } from './shared.service';
    
      @Component({
        selector: 'app-first',
        template: `
          <h2>First Component</h2>
          <input [(ngModel)]="data"/>
          <button (click)="setSharedData()">Set Shared Data</button>
          <app-second></app-second>
        `
      })
      export class FirstComponent {
        data: string;
    
        constructor(private sharedService: SharedService) {}
    
        setSharedData() {
          this.sharedService.setSharedData(this.data);
        }
      }
    

    2nd component

      import { Component } from '@angular/core';
      import { SharedService } from './shared.service';
    
      @Component({
        selector: 'app-second',
        template: `
          <h2>Second Component</h2>
          <p>{{ sharedData }}</p>
        `
      })
      export class SecondComponent implements OnInit{
        sharedData: string;
        constructor(public sharedService: SharedService) {}
    
        ngOnInit() {
         this.sharedData= this.sharedService.getSharedData();
        }
      }
    
  • Service Communication with Event Emitter

    Using an event emitter service we can broadcast events to multiple components that have subscribed to them.

    Let's create a service with an event emitter that emits values to its subscribers whenever a new value is emitted. Also, components that emit new value and subscribe to it.

    Service with an event emitter

      import { Injectable } from '@angular/core';
      import { Subject } from 'rxjs';
    
      @Injectable({
        providedIn: 'root'
      })
      export class SampleService {
        private messageEmitter = new EventEmitter<string>()
    
        emitMessage(data: string) {
          this.messageEmitter.next(data);
        }
      }
    

    1st component

      import { Component } from '@angular/core';
      import { SampleService } from './sample.service';
    
      @Component({
        selector: 'app-first',
        template: `
          <p>{{ data }}</p>
        `
      })
      export class FirstComponent {
        data: string;
    
        constructor(private sampleService: SampleService) {
          this.sampleService.messageEmitter.subscribe(data => {
            this.data = data;
          });
        }
      }
    

    2nd component

      import { Component } from '@angular/core';
      import { SampleService } from './sample.service';
    
      @Component({
        selector: 'app-second',
        template: `
          <button (click)="emitData()">Emit Event</button>
        `
      })
      export class SecondComponent{
        constructor(private sampleService: SampleService) {}
        emitData() {
          this.sampleService.emitMessage("Message from Second Component");
        }
      }
    

3)Subjects and Observables

Subjects and BehaviorSubject can be used for sharing states across multiple components.

A subject is observable that can both emit values ​​and subscribe to other observables. This makes it a versatile tool for exchanging data between components. When a new value is submitted to a Subject, all of its subscribers are notified. If there are no subscribers to a Subject, emitted values will be lost. It is commonly used for event handling or one-time notifications where the previous values are not necessary for subscribers.

A BehaviorSubject is a subclass of Subject that requires an initial value when created and always emits the latest value to its subscribers, even if they subscribe after emitting the value. This makes BehaviorSubject a good choice for sharing data between components that need to be synchronized.

Here is an example of how to use a Subject & BehaviorSubject with service to share data between components:

Service

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SharedService {
  ////Subject  
  private messageSubject= new Subject<string>();
  event$ = this.messageSubject.asObservable();

  emitEvent(data: string) {
    this.messageSubject.next(data);
  }
  ////

  ////BehaviourSubject
  private messageBehaviourSubject = new BehaviorSubject<string>("Hello");
  data$ = this.messageBehaviourSubject.asObservable();

  updateData(data: string) {
    this.messageBehaviourSubject.next(data);
  }
}

First component

import { Subject } from 'rxjs';

@Component({
  selector: 'app-first',
  templateUrl: `
    {{ data }}
  `,
})
export class FirstComponent {
  data: string;

  constructor(private sharedService: SharedService) {
    ////Subject
    this.sharedService.event$.subscribe(data => {
      this.data = data;
    });

    ////BehaviourSubject
    this.sharedService.data$.subscribe(data => {
      this.data = data;
    });
  }
}

Second component

@Component({
  selector: 'app-second',
  templateUrl: `
    <input [(ngModel)]="inputData" />
    <button (click)="updateData()">Set Shared Data</button>
  `
})
export class SecondComponent {
  inputData: string;

  constructor(private sharedService: SharedService) {}

  updateData() {
    ////Subject
    this.eventEmitterService.emitEvent("Data from Component B");

    ////BehaviourSubject
    this.sharedService.updateData(this.inputData);
}

4)ViewChild and ContentChild

  • ViewChild

    It enables a parent component to access the properties and methods of a child component.

    Code example:

    Child Component

    
      import { Component } from '@angular/core';
    
      @Component({
        selector: 'app-child',
        template: `
          <p>{{ message }}</p>
          <button (click)="onClickSubmit()">Submit</button>
        `
      })
      export class ChildComponent {
        message = "Submit to order!";
    
        onClickSubmit() {
          this.message = "Ordered";
        }
      }
    

    Parent component

    
      import { Component, ViewChild } from '@angular/core';
      import { ChildComponent } from './child.component';
    
      @Component({
        selector: 'app-parent',
        template: `
          <app-child #childComponentRef></app-child>
          <button (click)="submitOrder()">Modify Child Component</button>
        `
      })
      export class ParentComponent {
        @ViewChild('childComponentRef') childComponent: ChildComponent;
    
        submitOrder() {
          this.childComponent.onClickSubmit();
        }
    
  • ContentChild

    It enables a parent component to access and manipulate content projected into its template.

    Code example:

    Parent component

    
      import { Component, ContentChild } from '@angular/core';
    
      @Component({
        selector: 'app-parent',
        template: `
          <ng-content></ng-content>
          <button (click)="updateContent()">Update</button>
        `
      })
      export class ParentComponent {
        @ContentChild('contentRef') content;
    
        updateContent() {
          this.content.nativeElement.textContent = "Content updated!";
        }
      }
    

    Main component

    
      <app-parent>
        <p #contentRef>Content</p>
      </app-parent>
    

Did you find this article valuable?

Support Sankarshan Ramesh by becoming a sponsor. Any amount is appreciated!