Cover photo by Jessica Ruscello on Unsplash.
Presentational components are literally the user interface of our Angular application. They serve two purposes:
- Present application state to the user
- Change application state triggered by user interaction
To communicate with the rest of the application, presentational components have input properties for supplying them with data which will be formatted for display. They use output properties to notify application state changes initiated by user interactions. This is their data binding API.
Presentational components interface with users on one side and application state as well as other non-presentational layers on the other. They use container components as the glue that connect them to non-presentational application layers.
In relation to the rest of the application, data goes in, events emit out. Presentational components don’t care about where the data is coming from or where the events are going. When it comes to users, data is presented to them through the component template. Events from users come into the component through event handlers, that is methods that are bound in the component template using event bindings.
Can presentational components use property bindings as well? Sure, they can pass any piece of state down the component tree. Likewise, they might be forwarding events up the component tree from other presentational components similar to how they forward user-initialised events up the component tree.
Presentational components can be stateless, meaning their appearance and behaviour are always the same. Stateless presentational components are entirely about what is presented to the user.
In Angular, we have another building block for presentation: A directive. A directive should be our go to solution for presentation. It would be a poor choice to create a component to add a specific style like bold font or a background color. We would either use pure CSS for this or encapsulate the presentation in an attribute directive.
A good example of a stateless presentational component would be a toolbar component that simply projected content and wrapped a DOM structure around it for styling and layout purposes.
Presentational components can have their own isolated state. Think about a checkbox component. It has at least two states: Checked and cleared. The checkbox status is a piece of local UI state.
A checkbox component is stateful. What use is it, if that state is not somehow persisted though? The next time we return to this route, we would often expect the checkbox to be in the same state.
To be useful, this local UI state has to synchronise with the rest of the application state. However, if we added persistence logic to store the checkbox status in WebStorage, we would be dealing with a mixed component, not a presentational component.
To remain purely presentational, the checkbox component communicates its state changes to the rest of the application by emitting status events through an output property.
A stateful presentational component can have an initial state, regardless of the rest of the application. But to synchronise with the rest of the application, it needs a way to hydrate its state when the component is activated. The checkbox status will be governed by an input property.
The user might not be the only actor able to change the checkbox state. Maybe a timer sets the state to checked after 10 seconds for whatever reason. The checkbox component will be notified of this state change through its input property.
When following the Model-View-Presenter pattern, we keep our presentational components lean. We keep logic out of our component templates, but also our component models (the component class instances).
Component templates should do not much more than set up expression bindings for presentation and event bindings for user interaction.
Behaviour should be delegated to presenters which are component level dependencies that are completely isolated from the rest of the application. This ensures that the component model is only coordinating the configuration and binding of input properties, output properties, UI properties, and presenters.
The component model of a Model-View-Presenter-style presentational component contains no business logic except for glue code between the data binding API, UI properties, event handlers, and presenters.
We call them presentational components because they represent the presentational layers of our application such as presentation and user interaction as seen in Table 1.
Table 1. Horizontal layers of a web application. Open in new tab.
Preferably, we extract user interaction to component level services such as presenters as described in the "Lean presentational components" section.
Presentational components are usually reusable. Their data binding API or rendering API allow them to be used in many places.
We could easily have one-off presentational components though. For example, we could have a logo component that displayed our logo image. A logo component would often only be used in the main layout. Other layout components like a primary navigation component, a top app bar component or a side drawer component are other examples that are only used in one parent component but definitely have at least a presentational component part. They could also be split into container components and presentational components depending on their complexity.
A good example of a reusable presentational component is a button component. We could implement a design system for our organisation that includes a button. All developers in every team should be able to reuse the button component without worrying about the design system changing in terms of colors, fonts, or spacing. Or maybe we switched from Angular Material to Material UI. When the design system inevitably does change, the implementation details of our button component will make us able to make that change in a single place.
Presentational components are pure in the sense that they are free from side effects. Integration with state management, persistence, messaging, I/O, and other non-presentational layers belong in container components.
Because they are pure, they are deterministic in the way they render their DOM and emit events through their output properties.
Figure 1. DOM rendered based on 2 input values.
Figure 1 illustrates that when passed the input values
valueY, this presentational component's DOM will always be rendered in the composition
Figure 2. DOM rendered based on an input value and a user interaction.
In Figure 2,
valueX is input followed by a user interaction which is intercepted as
Event Y. This combination of input value and event series leads to the DOM composition
AxEy. This will always be the case when
Event Y happens while
valueX is input.
Figure 3. DOM rendered based on an input value. Event emitted based on input value and user interaction.
The presentational component in Figure 3 has the DOM composition
Ax based on
valueX being passed as an input. The user interaction intercepted as
Event Z leads to the value
eventZ being emitted through an output property.
This is always the case when
Event Z happens while
valueX is the input value.
We must be able to demonstrate the examples in Figures 1, 2, and 3 in tests. Otherwise our components are impure in that they depend on external state. If that’s the case, we need to create another input property and pass in that external state to turn the component into a presentational component with deterministic behaviour and rendering.
Presentational components become dirty as in needs to be dirty checked for one of two reasons:
- An external event such as a user interaction occurred and was picked up by an event binding in the component template
- New data was passed to one or more input properties
Because of this, we can optimise performance in our change detection cycles by using the
OnPush change detection strategy.
Read the full article for free on inDepth.dev to walk through a simple example of converting a mixed component to a presentational component after having extracted a container component from it.
You’l learn how to declare a data binding API, connect the component template to the data binding API, use minimal presentational logic and optimize change detection performance.
We go through an advanced example with multiple input and output properties. A component which contains form validation and multiple user interactions.
In the process of minimising presentational logic in the template, we convert a template-driven form to a reactive form.
Figure 4. The remove hero control flow with a presentational component.
Figure 4 shows an illustration of the control flow triggered by one of the user interactions in the advanced example.
Finally, the full article discusses these topics:
- Dynamic presentational components
- Testing presentational components
- Providing fake application state
- Characteristics of presentational components
- Converting a mixed component to a presentational component