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:
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.
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:
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.
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.
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:
private selectedRecipeSubject = new
BehaviorSubject<Recipe>({});
selectedRecipeAction$ =
this.selectedRecipeSubject.asObservable();
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() { }
}
We should update the shared selectedRecipe instance when the user clicks on one of the recipe cards in the dashboard:
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:
editRecipe(recipe: Recipe) {
this.sharedService.updateSelectedRecipe(recipe);
this.router.navigate(['/recipes/details']);
}
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:
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.
The previous pattern for sharing data has many benefits:
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.
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.
18.219.213.27