Separating responsibilities – Smart and Presentation components – Components and Pages
The information flow of a single-page application (SPA) can be quite complex and, if you don’t think about this flow from the beginning of your design, it can affect the productivity and quality of your project over time.
The simpler the better; therefore, a very common design pattern not only in Angular applications but also in SPAs in general is the composition of interfaces using Smart and Presentation components. In literature and in the community, you will also find this pattern under the name of Smart and Dumb components or Container and Presentation components.
A Smart component has the UI business rule; it is where we will have injected the services that will communicate with the backend and where the interface with the Presentation components will be composed.
A Presentation component is a component that has the sole purpose of showing the data passed by the Smart component, normally via input. A Presentation component in turn can contain one or more components of the Presentation type.
To illustrate this pattern, we will use the following diagram:
Figure 4.4 – Smart and Presentation components
Notice that we have a source of truth, which is the Smart component, and the communication occurs in only one direction, this is what we call a Unidirectional Data Flow. The purpose of this pattern is to isolate all states within a component and thereby simplify state management.
Let’s refactor our project to fit this design pattern. Let’s create a new presentation component using the Angular CLI:
ng g c diary/list-entries
In this new component, we are going to move the part that renders the list of diary entries into your template. In the list-entries.component.html file, add the following code:
<section class=”mb-8″>
<h2 class=”mb-4 text-xl font-bold”>List of entries</h2>
<ul class=”rounded border shadow”>
<li *ngFor=”let item of exerciseList; index as i; trackBy: itemTrackBy”>
<app-entry-item [exercise-set]=”item” />
</li>
</ul>
</section>
The list that will be displayed will come ready from the DiaryComponent component, so in the list-entries.component.ts file, we will add the following code:
import { Component, Input } from ‘@angular/core’;
import { ExerciseSet, ExerciseSetList } from ‘../interfaces/exercise-set’;
@Component({
selector: ‘app-list-entries’,
templateUrl: ‘./list-entries.component.html’,
styleUrls: [‘./list-entries.component.css’],
})
export class ListEntriesComponent {
@Input() exerciseList!: ExerciseSetList;
itemTrackBy(index: number, item: ExerciseSet) {
return item.id;
}
}
Here, we move the itemTrackBy function into the component, as it will be its function to display the list, and we include the exerciseList attribute with the @Input decorator. In this example, we didn’t specify any parameters, so the name of the template’s attribute will be the same as the attribute of the exerciseList class.
Let’s change the Diary template in the diary.component.html file to use the new presentation component we have created:
<main class=”mx-auto mt-8 max-w-6xl px-4″>
<app-list-entries [exerciseList]=”exerciseList” />
<button
class=”rounded bg-blue-500 py-2 px-4 font-bold text-white hover:bg-blue-700″
>
Add new entry
</button>
<br />
<br />
<button
class=”rounded bg-blue-500 py-2 px-4 font-bold text-white hover:bg-blue-700″
(click)=”newList()”
>
erver Sync
</button>
</main>
The DiaryComponent Smart component just passes the list to the ListEntriesComponent Presentation component, which iterates over the list by calling the EntryItemComponent Presentation component. With this structure, only the DiaryComponent component needs to worry about the list of exercises, respecting SOLID’s Single Responsibility concept.
We’ve studied how to structure our pages and components, but how do child components communicate with their parents? Let’s learn about the output attributes of Angular components next.