Briebug Blog

Sharing our thoughts with the community

How do I restructure and streamline my UI by using “container/presenter” pattern?

How do I restructure and streamline my UI by using “container/presenter” pattern?


Container/presenter is a pattern for organizing UI components based on their responsibility. This separation of concerns simplifies how applications are built while making them easier to test, maintain, and update over time. It also encourages loosely coupled components that allow for code reuse.


Container components:

  • Are concerned with how data is loaded by initializing and fetching data.
  • Are generally the outermost component that’s related to a feature or route.
  • Contain presenter and container components.
  • Respond to presenter component events.
  • Make stateful decisions.


Presenter components:
  • Are concerned with how things look.
  • Receive their data from container components through input bindings.
  • Respond to actions by delegating events up to their container components.


The following concepts will allow you to identify how components can be organized into a container/presenter pattern. You’ll understand the benefits of doing so and be able to write reusable, testable, and maintainable components. Your application and organization will gain predictable and extendable components that are easier to maintain and update.



The goal

We’re going to make a container component (dashboard) that controls how the data is fetched and a presenter component (shared/line-graph) that accepts any normalized graph values regardless of their data “type”.


The final file structure:



Source Code

This article has a companion repo that contains a working example of the two approaches.


  • The starting code is contained in src/app/+other-dashboard
  • The finished code is contained in src/app/+dashboard


Both modules contain similar functionality but the +dashboard folder contains less duplicate code even with an additional component that combines all the line graphs.



Starting from Scratch

In our fictional shipping company app, let’s focus on the /dashboard feature. Over time, feature requests have resulted in several components that display data in a line graph.


At first, we were asked to build a graph component of packages shipped over time. We created a new component, had it make an HTTP call to fetch data and displayed it in a graph. We made a nice self-contained component that we can reuse anywhere, right? Yes, but as we’ll see that component quickly becomes inflexible over the life of our application.


The inability to reuse our Packages Shipped component is realized as soon as you’re asked to build a new component that shows the history of Packages Returned using a similar graph. The problem reappears when we’re asked to build the Rushed Orders line-graph and continues with all the other scenarios we've yet to encounter. With the current pattern, we now have to build from scratch 2 more separate components that duplicate their interaction with our graphing library and fetching data.


Also consider if we need to control when or how often the data is fetched. What if you need to combine the packages shipped data with the packages returned in a new component graph? By combining the data fetching and display into a single component, it becomes difficult to share or reuse that data without duplicate code. You can see how quickly things are getting out of hand.


We can avoid this problem by making our Packages Shipped graph component a line graph presenter component and allow it to accept any type of data as long as it conforms to an API set forth by the component itself. This would allow our container to orchestrate when and what data is fetched and leave the graphing logic to the presenter component.



Getting started - Prerequisites

You’ll need a basic understanding of Angular Components and their Inputs and Outputs. Here's the starting state of our example app.

Looking at our example file structure above, we can see 3 packages-* components that are contained in our dashboard.component.html.


dashboard.component.html:

Since each of our packages-* components contain data fetching and display logic, our dashboard component is quite bare.


dashboard.component.ts:

Currently each package-* component handles everything it needs to function and is essentially a container component. They each display a line chart but with different data. For example, the code below shows the <app-packages-shipped> component and how it fetches historical data for the number of packages shipped.


packages-shipped.component.ts:

This pattern works and is fairly easy to follow. However, it can quickly lead to:


  • code duplication
  • inflexibility and difficulty with updates
  • difficulty in unit testing


As we can see, sharing this line chart and data will be quite difficult.


If we were to look at the other package-* components, we’d see that their display and functionality are almost identical. Let’s fix this by creating a line-graph component that can handle displaying nearly anything that’s passed to it.


line-graph.component.html:


dashboard.component.html:

Now we have a component that’s concerned with presenting the data it’s provided and delegating the events back to its container. This flexibility lets us use this component in multiple scenarios. Also, we can now remove the code duplication across the 3 packages-* components and move that data fetching logic into the dashboard.component.ts file. Let’s do that next:


dashboard.component.ts:

We can see that our dashboard component now contains the 3 data fetching calls and transforms the data so that it’s in the correct format for the line-graph. We’re also responding to events emitted from the line-graph component with the titleClicked method. Let’s see how the template for the dashboard changed:


In the dashboard.component.html file, there are still 3 components, but they’re all the same reusable presenter component. We’ve reduced the number of distinct components that need duplicate logic. Now when a change is requested across all the charts in our Dashboard feature, we only have to change them in one place.


When presenter components are designed to be generic, their flexibility allows you to extend them to handle edge cases based on the data or flags provided. If a use case becomes too difficult to account for, you always have the option to create another presenter that better handles the similar but alternate use cases.


We’ve also made our testing strategy simpler. By moving the data fetching and manipulation logic to the dashboard.component.ts file, we can focus our testing efforts there and not across three previously separate component spec files. Alternatively, the line-graph component can be tested thoroughly for display acceptance criteria.



Summary

After the refactor you should have removed multiple directories and files. The DashboardComponent is the container component and it contains the LineGraphComponent presenter component in its view. Each has a specific job, gathering data and displaying data respectively.


This application is now easier to maintain and more flexible when dealing with changes and new features while being easier to unit test.


Business values:


  • Reduces risk - unpredictable user experience and bugs
  • Removes complexity - code optimization, streamlined output/releases
  • Reduces operating costs - Reduced time, effort, and inefficiency while avoiding duplication
  • Increases performance - through Angular's component Input change detection mechanisms


This approach provides predictable feature velocity and stability while developing an application over its life cycle. It streamlines development and removes duplicate code which in turn removes the surface area for regressions and bugs.


As a developer, you’ll be better equipped to deliver features by creating a collection of building blocks that can be quickly combined to meet acceptance criteria in a predictable manner.


Once you start building reusable components, it's easier to recognize similarities and patterns across features and applications. Presenter components are great candidates for sharing code either internally or externally and can save yourself and others time when developing.

View Details
- +
Sold Out