Component reusability in Angular
Component reusability is a key concept in web development, it is a known characteristic to measure quality of our code. In addition, the use of this process increases productivity and code quality and decreases cost and implementation time. Some developers tend to use first-hand, trivial and naive approaches in favor of carefully designed ones in order to save time and effort, which leads to redundancy and sloppiness. This is the unfortunate status quo of some applications. However, if we understand this concept, we will become more aware of good practices and implementing them will skyrocket our productivity.
The best way to explain this concept is by telling a story. You are a developer and one day you receive a task to create a widget and put it on your website. You create a template, apply styles to it and write functionality for this component. For the sake of this article, I created a simple widget that shows random job offers to the user with the option to refresh and show a new offer or display the details of a specific offer.
For example, your template code can look like this-
…and your class can look like presented below-
Quite astonishingly, your widget receives recognition and is systematically used by users. Your team is happy with the result and plans to create similar widgets and put them on the website. The only requirement is to leave out the header, divider, buttons and make the section interchangeable. Buttons must perform their assigned tasks regardless of what is inserted in the section slot. It can be a weather or gallery widget, whatever it is. Something that can be displayed with detailed information or have something to load next. Sounds like a challenge!
First solution: clone original component
We can create a new component, e.g: WeatherWidgetComponent, copy the template, styling, class functionality, change the section’s content and invoke completely new method from a different service. It shouldn’t take too long, but it is a bad solution nevertheless and should be avoided at all costs. Firstly, it exploits redundancy, secondly, it violates the DRY principle (Don’t Repeat Yourself) and lastly it introduces bad practices in the project and lowers the quality of the application.
Second solution: configurable component
Let’s create some flags and pass them as inputs to the component and based on their values show content conditionally using *ngIf. For example:
It should work as we intended, but it is not a good solution either way. We can have n widgets, thus we need n flags. The code will get very messy and hard to maintain, also this approach is not flexible. We can do better!
Third solution: Bridge pattern
Let’s go back to the requirement again and read it carefully. Everything that we put into the section needs to work with our buttons. Basically, it needs to follow a contract. At this moment, we have a template that includes header, divider and two buttons and the only thing we need to do is pass something in to the section slot that has to comply with a predefined contract. That something is an implementation. We need to create a group of implementations (sections like advert section, weather section) and wrap them around with an abstraction. The implementations need to follow a common contract in order to work with the wrapper that holds the abstracted buttons’ logic. If the contract is obeyed and the implementations hold correctly created logic, the wrapper can easily access the implementations and invoke their methods. Luckily for us, there is a structural design pattern, that will help us to solve the problem, called Bridge pattern.
Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies — abstraction and implementation — which can be developed independently of each other.
Let’s create a wrapper component that will play the role of our abstraction. Move the header, divider and the buttons to a new component and with the help of content projection create a slot in the section tag, so we can inject specific implementation into it.
Pass your desired implementation in to the wrapper component.
Before writing our logic in the wrapper class, create a contract, which all widget implementations need to follow.
Go to your implementation and implement the interface.
Write down the logic for the wrapper class.
It works, but this is not what we want yet. We rely on a specific implementation rather than on abstraction. This wrapper needs to work with multiple implementations, not a single one. In order to make it work correctly, we need to create another abstraction layer that can be represented as a token. Now, by providing this token, Angular has to resolve a specific component instance and give it to us, so we can access it. This is where dependency injection comes in. Generally speaking, we can provide an abstraction to Angular and it will respond to us with a concrete implementation. Let’s declare an injection token.
Replace the concrete implementation with the token.
We expect that after resolving the token, we’ll receive a class instance (implementation) which implements the Widget interface (contract). However, there is a final step to complete. Injecting the token is not enough, we have to provide it, saying more specifically, we need to set up a provider in our widget component and bind with wrapper’s token with the widget component itself.
We went from a widget component that the wrapper knows nothing about to an interface that it does and every widget component provides that interface with its own type. Finally, we apply the same approach with new widgets.
Forth solution: view templates
This is the most commonly used approach. We create template chunks and pass them to the wrapper component with the aid of content projection. The main benefit of this solution is that we can create multiple versions of the same widget — use different texts, styling and pass different content.
Each view template has its own directive applied. Moreover, each directive exposes a reference to its view template and thanks to that we can consume the content placed inside the template. Next thing are the variables declared beside the directives. They’re just placeholders, a API interface which the consumer component needs to provide implementation for it. Let’s create appropriate directives.
Now, let’s go to the wrapper component and grab the template references with the help of our directives we have just created. Besides that, we will also create context objects that will provide proper implementations for the view templates. If a context property name is not provided, we need to set our property to $implicit.
Finally, let’s render our view templates in appropriate slots with the help of NgTemplateOutlet directive.
Conclusion
I truly believe this article will be helpful for every Angular developer who looks for good practices and wants to use them in their projects. Furthermore, this article covers few advanced concepts as DI, design patterns, content projection, view templates and so on, which can bring a new light on how to build components in your application. Knowledge presented here might be a refresher for many advanced developers or it can be a new experience for people who began their journey with the Angular framework.
That’s it. Thanks for reading. If you liked the post, please give me an applause. If you have any questions, feel free to ask them in the comments!