Creating Reusable Angular Components – How To Avoid the Painful Trap Most Go In

Share on facebook
Share on google
Share on twitter
Share on linkedin

To reuse where it is possible is a natural instinct in all aspects of life. The idea that something can serve other purposes in contrast to consuming more is beautiful and sustainable. In reality, how easy an item can be reused is determined by how tightly it is coupled to a specific purpose. The same applies to software development, but it takes some know-how to efficiently create reusable components that will be flexible enough for your use cases while being worth the effort of making it reusable.

When having multiple teams working together it makes sense to reuse components between projects. There are two main ways to create reusable components in Angular:

  1. Pass inputs to the component, passing the necessary data to the component used for rendering and configuring the component. This normally involves iterating over the provided data and follow a convention for how to render the data.
  2. Use transclusion/content projection to pass a template to the reusable component and show the templateRefs inside the component using the ngTemplateOutlet directive or ng-content

The choice of which technique to reuse components you should use is determined by the desired flexibility. If you have a simple reusable component that doesn’t need to be very flexible, simply using inputs will do. An example of this is a simple questionnaire that should be dynamically rendered using json data from an API.

On the other hand, this becomes a pain when you need to pass lots of inputs to the component to provide the necessary data to the component. For example, you might need a fieldsDefinitions and actionDefinitions input for determining the fields and different actions in a list and inside the reusable component, you then need to have a lot of logic to render the input data. I have seen cases here where this has escalated to creating a dedicated DSL for rendering components. This gets very painful as the amount of input keeps growing, as well as the complexity of the reusable component as it should handle more edge cases, in the end making the component harder to reuse than if it were simply copied and modified to serve the purpose.

What you want to do instead is allow for an external template to plug into the component using either templateref or ng-content (check my plugin architecture post for an example of this with ng-content). In summary, use template projection when more flexibility is needed for the reusable component.

Should you use template reference or ng-content?

There is a subtle difference between using templateRef vs. using ng-content because of how Angular’s lifecycle management works. Angular’s OnInit and onDestroy hooks works for component where they are declared, not where they are used/rendered. That means, if using ng-content, the child will not be destroyed when destroying the component containing the ng-content. Also for a child component being instantiated with ng-content, the constructor and init hooks will also be invoked regardless of if the child component has been rendered in the DOM. For that reason, passing the template projection as templateRef is the most maintainable and performant, as the lifecycle hooks are only getting called if the templateRef have actually been rendered in the DOM and because it gets destroyed with the component instantiating the templateRef.

Creating a reusable card/list view component

To illustrate how you should keep components reusable and maintainable we are going to create a reusable card list component, which can toggle between cards and a list. This is based on my Angular todo app demo, a simple TODO management application:

The purpose of creating this feature is to learn how more complex reusable components can be created without going in the trap of needing to create a lot of input data and maintain a convention for how the reusable component should be rendered based on this data. Been there done that, wasn’t fun. What we are going to do instead is using transclusion, that is passing template references to the reusable component. This is going to cause slightly more duplication but easier use and maintenance of the reusable component. The point is: less code duplication is not beneficial it if it makes the code harder to use and maintain.

We want to create a card-list component that takes in a listRef, cardRef and data to be shown and is used like this:

Note how simple the interface of the cards-list component is because we simply utilize templateRefs and map data using let-todos="data" which will map data to todos when we are passing data to a templateRef with ngTemplateOutletContext.

We are then going to create the card-list component.

Open the terminal, go to shared folder and type:

ng g m cards-list

Go to cards-list and create the cards and list components:

ng g c cards

ng g c list

We should now have:

Since the only input we are working with here is template refs and data, to be shown in the template refs, we are going to have very simple presentation components. The list component looks like this:

To render this it only takes in a listRef and the data to render the list.

The cards component template is slightly different because it is iterating over each item (todo item in this case), and are rendering them using ngTemplateOutlet and is setting the data for the ngTemplateOutlet with ngTemplateOutletContext. It is setting the data which in our templateRef is passed to the todo data using let-todo="data".

Some styling is applied to these cards to make them wrap nicely:

Now we can display the card-list component and easily change the cards or list by simply changing the template ref provided.

The card and list row components are created like presentation/dumb components in the shared folder:

The card component is created with Angular Material directives:

The list row is created with Bootstrap (got to spice stuff up):

These are being used as template references in the reusable component.

The complete demo can be found here:

Conclusion

In this post, we looked at two ways of creating a reusable component. The first way only works for simple components as they will become harder to use and maintain if the complexity grows because of lots of input to be configured for the specific use case and as well as a lot of “duct tape” programming to handle all the different applications of the reusable component. The way of handling this is by using transclusion instead of using input data and conventions of how to render this. Transclusion using templateRef is preferred over ng-content performance and maintenance wise because it keeps the life cycle in sync with where it is used as well as supporting conditional instantiation.

To put all this into practice we created a card list reusable component for rendering an array of data as either a card or a list. The naive approach to creating this would be to create a lot of inputs for being able to render this by iterating over data to determine rows and actions in lists and cards. This quickly becomes tiresome because it is not scaling to more complex usages. What we do instead is we used templateRefs for a card and a list, as well as the data to display, and created this reusable component in an easily maintainable way using only three inputs. The lesson of the day is: reusable components should be easy to use as well as easy to maintain.

If you liked this post, make sure to follow me on Twitter, subscribe for weekly blog posts and give some feedback in the comment section.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »

How to Set Up Git Hooks in an Nx Repo

Git hooks can be used to automate tasks in your development workflow. The earlier a bug is discovered, the cheaper it is to fix (and the less impact it has). Therefore it can be helpful to run tasks such as linting, formatting, and tests when you are e.g. committing and pushing your code, so any

Read More »

The Stages of an Angular Architecture with Nx

Long gone are the times when the frontend was just a dumb static website. Frontend apps have gotten increasingly complex since the rise of single-page application frameworks like Angular. It comes with the price of increased complexity and the ever-changing frontend landscape requires you to have an architecture that allows you to scale and adapt

Read More »

The Best Way to Use Signals in Angular Apps

Since Angular 16, Angular now has experimental support for signals and there is a lot of confusion in the community about whether this is going to replace RxJS or how it should be used in an app in combination with RxJS. This blog post sheds some light on what I think is the best way

Read More »

High ROI Testing with Cypress Component Testing

Testing is one of the most struggled topics in Angular development and many developers are either giving up testing altogether or applying inefficient testing practices consuming all their precious time while giving few results in return. This blog post will change all this as we will cover how I overcame these struggles the hard way

Read More »