I came across the pattern of separating your components into Presentational and Container Components (also known as Smart and Dumb components) while playing around with React/React Native a couple of years ago. I have not seen the same level of adoption in the Angular world for this pattern, so I thought I’ll explore it a little bit here. Let’s start with a little background:
- are purely user interface and concerned with how things look.
- are not aware about the business logic, or services.
- receive data via @Inputs, and emit events via @Output.
- contain all the business logic.
- pass the data to the Presentational Components, and handle @Output events raised by them.
- have no UI logic.
- do have dependencies on other parts of your app, like services, or your state store.
Creating the Components
To create a simple example, I am going to use the final code from the Tour of Heroes tutorial as my starting point. I’ll focus on ‘HeroDetailComponent’ for this post. The initial template and the corresponding component typescript code is below:
This is currently a mixed component, since we are dealing with services like the HeroService, as well as defining the look and feel of the Hero Detail Component. We can split it up into 2 components, a Container component that contains all the business logic, and a Presentational Component that will live inside the Container component, and define the UI.
Let’s start with the Presentational component. We already have an @Input for the hero to bind to. Let’s add 2 @Outputs corresponding to the click of the “go back” and the “save” buttons, and emit events when those buttons are clicked. The code for the template and the component should look like:
Next we can create the Container component. The template will just interact with the Presentation component we defined above, and pass it the data it needs, as well as handle the events raised by it. In the component code, we can initialize the hero in the constructor, and add event handlers for saving the details, and going back to the previous screen. The code is below:
Note: To get the app to work again, you will have to register the new Container Component in the app module, and also update the route file (app-routing.module.ts) to point to the Container Component instead of the old one.
Container component templates have no logic in them to test. Therefore, we can bypass the whole Angular TestBed setup. Since Angular compiles components for every test case, we are skipping compilation, DI and component lifecycles. This results in simpler and faster unit tests. I have created some sample unit tests for our new container component.
The code shown in this post can be accessed here. Some of the advantages of taking this approach are:
- There is a better separation of concerns. The presentation components promote reuse, and you eventually have a library of UI Components
- The Container components are easier to test, and the tests run faster, since you no longer have to do the Angular TestBed setup.
- You can improve your application performance by adopting OnPush Change Detection in your container components. As per the Angular documentation
This article explains it better.
Use the CheckOnce strategy, meaning that automatic change detection is deactivated until reactivated by setting the strategy to Default (CheckAlways). Change detection can still be explicitly invoked.
With the default strategy, any change (user events, timers, XHR, promises, etc.,) triggers a change detection on all components. With OnPush strategy, the component only depends on its @inputs(), and needs to be checked if
- the Input reference changes
- A DOM event originated from the component or one of its children.
- Change Detection is run explicitly.
I hope you found this useful. If you would like to explore the ideas expressed in this post further, Lars Gyrup Brink Nielsen wrote a bunch of articles where he takes this concept to the next level and implements the MVP pattern in Angular.