Chapter 10: Sharing Data between Components

Sharing data between components is a very common use case in web applications. Angular provides many approaches to communicate between parent and child components, such as the popular @Input and @Output decorator patterns. The @Input() decorator allows a parent component to update data in a child component. The @Output() decorator, on the other hand, allows the child component to send data to a parent component. That's great, but when data needs to be shared between components that are either deeply nested or not immediately connected at all, those kinds of techniques become less efficient and difficult to maintain.

So, what's the best way to share data between sibling components? This is the heart of this chapter. We will start by explaining our requirement; then we will walk you through the different steps to implement the reactive pattern for sharing data between sibling components. Finally, we will highlight the other alternatives for sharing data and handling your application's state.

In this chapter, we're going to cover the following main topics:

  • Defining the requirement
  • Exploring the reactive pattern to share data
  • Highlighting other ways of sharing data

Technical requirements

This chapter assumes that you have a basic understanding of RxJS.

The source code of this chapter is available at https://github.com/PacktPublishing/Reactive-Patterns-with-RxJS-for-Angular/tree/main/Chapter10.

Defining the requirement

Let's assume that we have components (C1, C2, C3, and C4) that do not have any relationship with each other and there is information (DATA) shared between those components:

Figure 10.1 – Shared data between components

Figure 10.1 – Shared data between components

The components can update DATA and consume it at the same time. But at any time during the process, the components should be able to access the last value of DATA.

Well, this is the requirement in an abstract way. Let's make it clearer with a concrete example.

In our RecipeApp, when the user clicks on one recipe, it gets selected, and we want all the components to have access to the last-selected recipe by the user.

Exploring the reactive pattern to share data

Angular services are powerful and efficient for creating common references for sharing both data and business logic between components. We will combine Angular services with Observables, more specifically BehaviorSubject, to create stateful, reactive services that will allow us to synchronize the state efficiently across an entire application. So, in the following subsections, let's explain the steps to implement a reactive pattern to share data between unrelated or sibling components.

Step one – creating a shared service

We will create an Angular service called SharedDataService using the Angular CLI, as usual under the src/app/core/services folder:

ng g s SharedData

In the SharedDataService class, we need to create the following:

  • A private BehaviorSubject called selectedRecipeSubject to multicast the value of the currently selected recipe, which represents the data to be shared. It is a strongly typed BehaviorSubject. The type of its emissions will be Recipe. We initialize it with an empty object as the default value, since initially, we don't have any selected value. We define the selectedRecipeSubject value as private to protect it from external use; otherwise, any external process could call the next method and change the emissions, which is dangerous:

      private selectedRecipeSubject = new

       BehaviorSubject<Recipe>({});

  • A public observable that is extracted from selectedRecipeSubject to handle data as an observable. We used the asObservable() method available in the Subject type. The emissions of this observable are consumed in read-only mode. External processes can only consume its emissions, not change them:

      selectedRecipeAction$ =

       this.selectedRecipeSubject.asObservable();

  • Finally, we need to define a method that will update the shared data, which is the selected recipe. This method only calls next on selectedRecipeSubject to notify all subscribers of the last-selected recipe passed as a parameter. The process that updates the selected recipe will call this method:

    updateSelectedRecipe(recipe: Recipe) {

        this.selectedRecipeSubject.next(recipe);

      }

This is what the service looks like after putting all the pieces together:

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

import { BehaviorSubject } from 'rxjs';

import { Recipe } from '../model/recipe.model';

@Injectable({

  providedIn: 'root'

})

export class SharedDataService {

  private selectedRecipeSubject = new

   BehaviorSubject<Recipe>({});

  selectedRecipeAction$ =

   this.selectedRecipeSubject.asObservable();

  updateSelectedRecipe(recipe: Recipe) {

    this.selectedRecipeSubject.next(recipe);

  }

  constructor() { }

}

Step two – updating the last-selected recipe

We should update the shared selectedRecipe instance when the user clicks on one of the recipe cards in the dashboard:

Figure 10.2 – The list of recipes

Figure 10.2 – The list of recipes

So, in order to update the shared selectedRecipe when the user clicks on the card, we have to add the (click) output that calls the method that will perform the update. In our case, it is the editRecipe(recipe) method. This is the HTML code snippet:

<div class="rp-data-view" *ngIf="filtredRecipes$ |async as recipes" class="card">

    <p-dataView #dv [value]="recipes" [paginator]="true"

     [rows]="9" filterBy="name" layout="grid">

        <ng-template let-recipe pTemplate="gridItem">

            <div class="p-col-12 p-md-3">

                <div style="cursor: pointer;"

                 (click)="editRecipe(recipe)" class=

                 "recipe-grid-item card">

            </div>

            </div>

        </ng-template>

    </p-dataView>

</div>

In RecipesListComponent, we implement the editRecipe method, which takes as input the selected recipe and performs two actions:

  • It updates the last-selected recipe by calling updateSelectedRecipe(recipe:Recipe), which we created in SharedDataService. So, we should inject the SharedDataService service in RecipesListComponent.
  • It displays the details of the recipe in RecipeDetailsComponent:

      editRecipe(recipe: Recipe) {

        this.sharedService.updateSelectedRecipe(recipe);

        this.router.navigate(['/recipes/details']);

    }

Step three – consuming the last-selected recipe

In the RecipeDetails component, we need to consume the last-selected recipe in order to display the details of the last-selected recipe. So, again, we need to inject SharedDataService and subscribe to the selectedRecipeAction$ public observable, which will emit the last-selected recipe, as follows:

export class RecipeDetailsComponent implements OnInit {

  constructor(private sharedService: SharedDataService) { }

  selectedRecipe$ =

   this.sharedService.selectedRecipeAction$;

  ngOnInit(): void {

  }

}

That's it! This is how you can share data between unrelated components throughout the application.

Now, we can use the latest value of the recipe everywhere. We only have to inject SharedDataService into the component that needs the shared data and subscribe to the public Observable that represents the read-only value. We can, for example, add this code in HeaderComponent to display the title of the last-selected recipe in the application's header:

<div *ngIf="selectedRecipe$ | async; let recipe">

    <span> {{recipe.title}} </span>

</div>

So, if we change the shared value in any of the components, all the other components will get notified to update their processes.

We used this pattern in Chapter 6, Combining Streams, to share the value of the filter in RecipesFilterComponent with RecipesListComponent, and then we combined the streams to display the filtered results.

To summarize everything, here's a visual representation of the reactive pattern steps:

Figure 10.3 – The GET HTTP requests overhead

Figure 10.3 – The GET HTTP requests overhead

  1. Create a shared Angular service.
  2. Define a private BehaviorSubject that will multicast the shared value to the subscribers. You need to specify the type of it, and it should be initialized with the default value of the shared data. As we use BehaviorSubject, late subscribers will always get the last value emitted, since BehaviorSubject stores the last-emitted value. Each consumer component should access the same copy of the data. BehaviorSubject here is only handled inside the shared service.
  3. Define a public Observable that will hold the read-only shared value.
  4. Create the update method inside the service that will update the shared value by calling the next method in the Subject type and passing the last value.
  5. Inject the service in the component that updates the value of the shared data and call the update method implemented in the service (step 4).
  6. Inject the service in the component that consumes the value of the shared data and subscribe to the exposed Observable in the service.

As far as I'm concerned, this is the simplest way to share data between unrelated components in Angular. But there are better ways for more complex scenarios; let's highlight them in the next section.

Highlighting other ways for sharing data

The previous pattern for sharing data has many benefits:

  • Improves the sharing of data between unrelated components
  • Manages mutability risk
  • Makes communication between components easier

We can use Angular services and RxJS's BehaviorSubject to manage our application's state. This works perfectly in many situations, but for big applications where there are a lot of user interactions and multiple data sources, managing states in services can become complicated.

In this case, we can use a state management library to manage the state of our application, which is the main goal of state management libraries.

There are many great state management libraries out there to manage states in Angular. The most popular is NgRx. All the state management libraries have one thing in common – they are built on top of RxJS Observables and the state is stored in BehaviorSubject.

Note

For further details about NgRx, please refer to https://ngrx.io/guide/store.

Summary

In this chapter, we explained the motivation behind sharing data between components and learned how to implement it in a reactive way. We learned how we can use BehaviorSubject combined with AngularServices to share data between unrelated components and manage our application state. Finally, we went through the alternatives of state management for more complex scenarios. This will help you put in place a good architecture for your web application, which will make it more reactive, more performant, and reduce the cost of maintainability.

In the next chapter, we will explore the pattern of bulk operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.213.27