How to Share Data Between Components in Angular Without a Parent-Child Relationship

·

6 min read

Featured on Hashnode
How to Share Data Between Components in Angular Without a Parent-Child Relationship

We build robust front-end applications using Angular. In Angular, the UI elements are separate components and each component can communicate to other components, thus maintaining an interactive behavior, while they keep stand-alone components. We know that in Angular, a component can contain another component in its template to maintain a parent-child relationship. However, what if the components are not connected? What if they are two separate stand-alone components without a parent-child relationship?

Let’s assume our components are configured as below.

Component structure in Angular

If you check this diagram, you see that our App Component calls two separate components SelectionComponent and AccountComponent, which don’t have a parent-child relationship. Also, let’s look at how AppComponent calls them in its template.

‌app.component.ts

<app-selection></app-selection>

<app-account></app-account>

Angular Service comes to the rescue!

In situations like this, we can use a service to communicate the data between these components. Take a look at the below diagram.

Component structure with service injected in Angular

Let’s start by creating a new Angular project to describe how this will work.

💡
Here, before we begin, it's worth mentioning that I'm using Angular v.18 as that's the most updated version as per date. You can see in the screenshot, my current Angular version is 18.2.1, and Node version is 20.17.0.

Angular version check in warp terminal

Start a new Angular project with this command.

ng new angular-parent-child

Once the project is built, navigate to the project and create two components ‌ SelectionComponent and ‌ AccountComponent.

ng g c components/selection
ng g c components/account

Let’s remove everything from app.component.html and include the below lines to call them from AppComponent.

app.component.html

<app-selection></app-selection>

<app-account></app-account>

app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { SelectionComponent } from './components/selection/selection.component';
import { AccountComponent } from './components/account/account.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, SelectionComponent, AccountComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'angular-parent-child';
}

Use SelectionComponent to select an account

In the SelectionComponent we will instantiate a button for 3 accounts, and based on the selection, we want to show the amount in these accounts.

‌selection.component.ts

import { Component } from "@angular/core";
import { AccountService } from "../../service/account.service";

@Component({
  selector: "app-selection",
  standalone: true,
  imports: [],
  templateUrl: "./selection.component.html",
  styleUrl: "./selection.component.css"
})
export class SelectionComponent {

  accountName1: string = "Account 1";
  accountName2: string = "Account 2";
  accountName3: string = "Account 3";

  amount1: number = 100;
  amount2: number = 200;
  amount3: number = 300;

  constructor() {}

  updateAccount(accountName: string) {

  }

}

selection.component.html

<p>Select account:</p>

<button (click)="updateAccount(accountName1)">{{accountName1}}</button>
<button (click)="updateAccount(accountName2)">{{accountName2}}</button>
<button (click)="updateAccount(accountName3)">{{accountName3}}</button>

Notice the function updateAccount(accountName) takes accountName as input and it's not yet implemented. We are going to implement it in a minute.

Use AccountComponent to display the amount

In AccountComponent we define an attribute amount to capture amounts for the accounts.

account.component.ts

import { Component } from '@angular/core';
import { AccountService } from '../../service/account.service';

@Component({
  selector: 'app-account',
  standalone: true,
  imports: [],
  templateUrl: './account.component.html',
  styleUrl: './account.component.css'
})
export class AccountComponent {

  amount: any;

  constructor() {}

  ngOnInit() {

  }

}

account.component.html

<p>Amount in selected account:</p>

<p>Amount: {{amount}}</p>

Now at this point, you can run the server by the command below and check in the browser, both the components are rendered properly.

ng serve -o

localhost:4200

Creating Account Service

As we have our components setup, let's go ahead and create our service using the below command, which will create AccountService in the directory src/app/services/account.

ng g s services/account

Let's have amount field to hold amount for accounts.

account.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class AccountService {

  constructor() { }

  private amount = new BehaviorSubject(0);
  getAmount = this.amount.asObservable();

  setAmount(amount: number) {
    this.amount.next(amount);
  }
}

In the above code:

  1. We have a BehaviorSubject called amount and we are assigning the initial value to be 0.

  2. We created an observable called getAmount, which our components can subscribe to get the amount.

  3. We created method setAmount which takes the amount and assigns it to our private variable this.amount.

Injecting Account Service

1. Retrieve the amount from AccountService using AccountComponent

As we have the service available, now go ahead and inject our service in AccountComponent and get the amount by subscribing to the observable getAmount in AccountService. Once we get the amount value, let's assign it to the component variable this.amount.

import { Component } from '@angular/core';
import { AccountService } from '../../services/account.service';

@Component({
  selector: 'app-account',
  standalone: true,
  imports: [],
  templateUrl: './account.component.html',
  styleUrl: './account.component.css'
})
export class AccountComponent {

  amount: any;

  // injecting accountService in selection.component.ts 
  constructor(private accountService: AccountService) {}

  ngOnInit() {
    this.accountService.getAmount.subscribe(amount => {
      this.amount = amount;
    });
  }

}

After the code is compiled, let's check what amount got retrieved in our Angular application on localhost:4200/

localhost:4200

Angular app rendering components with service injected

Check that amount 0 was retrieved. Remember we had set the default value for amount BehaviorSubject to be 0 by this code private amount = new BehaviorSubject(0);. That's why it's retrieving this to be 0 for now.

So, now completed a component to service communication to retrieve amount.

2. Set amount to AccountService using SelectionComponent

Go ahead and inject our service in SelectionComponent and set amount based on which button was clicked.

selection.component.ts

import { Component } from '@angular/core';
import { AccountService } from '../../services/account.service';

@Component({
  selector: 'app-selection',
  standalone: true,
  imports: [],
  templateUrl: './selection.component.html',
  styleUrl: './selection.component.css'
})
export class SelectionComponent {

  accountName1: string = 'Account 1';
  accountName2: string = 'Account 2';
  accountName3: string = 'Account 3';

  amount1: number = 100;
  amount2: number = 200;
  amount3: number = 300;

  // injecting accountService in selection.component.ts 
  constructor(private accountService: AccountService) {}

  updateAccount(accountName: string) {
    if (accountName === this.accountName1) {
      this.accountService.setAmount(this.amount1);
    } else if (accountName === this.accountName2) {
      this.accountService.setAmount(this.amount2);
    } else if (accountName === this.accountName3) {
      this.accountService.setAmount(this.amount3);
    }
  }

}

In the code above, we are doing the following:

  1. We inject AccountService in SelectionComponent through the constructor.

  2. updateAccount method takes a name and based on the account name, it sets the amount by calling setAmount() method in our newly created AccountService.

Now let's update the template for SelectionComponent.

selection.component.html

<p>Select account:</p>

<button (click)="updateAccount(accountName1)">{{accountName1}}</button>
<button (click)="updateAccount(accountName2)">{{accountName2}}</button>
<button (click)="updateAccount(accountName3)">{{accountName3}}</button>

Let's check our server on localhost:4200/ and check the amount. Initially this would be 0. Now let's click on any of the account buttons and check how the amount gets updated. For example, if we click on Account2 button, the amount changes to 200.

Clicking other buttons like Account1 and Account3 will update the balance to 100 and 300 respectively.

So, now we are able to share data from SelectionComponent to the AccountComponent using a service, whereas AccountComponent is not a child-component.

If you find any issues or concerns rendering the project, please check the GitHub repository to troubleshoot. The project code should be available for clone in this GitHub repository: https://github.com/nazislam/angular-parent-child

Hope you enjoyed the blog. I'm working on building a SaaS product to improve productivity for professionals and sharing everything in my discord channel. Join my Discord channel for free by clicking this link: discord.gg/XmbrDrnVhr