Back to the homepage
Angular

Angular – slightly different approach to personalising component templates

Component customization is a topic familiar to anyone who has at least once used an external UI library such as primeng. The ability to define the whole template without modifying the code of the original component is useful for the programmer and provides better reusability. So, due to the many advantages, why not use such an approach in your own applications or libraries? ?

There are several different implementations of this functionality, which I have experimented with many times during my daily work. In this article, I will pose a review of popular solutions, and then present the implementation of the best one, in my opinion. The inspiration came from the Github sources of the primeng library, so it’s not my own method, it’s based on the work of other programmers. I will also try to justify my choice. So here we go.

Case study

Let’s assume that we need a widget with user information. It should contain some typical data like first name, last name, email, and avatar. The component template could look like this:

Let’s also imagine that we require several different ways of displaying our tile. The displayed data will not change, but the layout will and using CSS won’t be enough. Let’s also assume that we want to be able to define a separate template for the avatar and for the rest of the user data.

So, now that we have defined the problem – let’s take a look at possible solutions.

Step 1: flags

Probably the easiest approach to implementation is using flags. Imagine that a widget component contains one or more properties with the @Input() decorator, used to control the appearance of the template. And that’s it?

The implementation here is very simple. However, the approach has a big disadvantage – the component must “know” all possible variants of use, which must be predefined and contained inside the template. Unfortunately, this excludes the creation of general-purpose components ?. 

We have to keep on looking.

Step 2: content projection

Angular has a ng-content directive, used for what in English is nicely called “Content Projection”. A detailed description can be found in the documentation, but in short, it allows us to pass a fragment of a template inside a component directly between its tags. We can use many such directives and specify a selector of the elements which will be “matched“, for each one.

Let’s try to implement our component using content projection:

Then its use would look roughly like this:

Once again the advantage is the simplicity of implementation and use. Disadvantages: unfortunately there are a couple. It’s hard to define a default template and passing it with every usage may number of repeated code. Moreover, the component that we create is not able to influence the data displayed inside. It is just a wrapper for what we pass. In consequence we have to have access to all data used within the component in the place in which we want to use it. In the examined case it’s not so noticeable, but let’s imagine defining templates for diverse table elements – rows, headers and footers. Using the component we do not wish to dig deeper into the data or process them in any way – this functionality should be part of its internal implementation. We’re one step closer to the goal, but still not there yet.  

Step 3: ng-template

The ng-template directive, as the name suggests, wraps the template that will be inserted in one or more places during the rendering of the final document tree. It is not presented as such at the place of occurrence – for that we need additional elements, such as the ngTemplateOutlet directive. The details can easily be found in the framework’s documentation.

What we are most interested in is the configurable context – the configurable context. It means that we can define what the template “sees” when it is inserted into the document tree. As a consequence, I can define a template for another component, referring to the variables inside the component and not available outside of it.

Sounds promising? Let’s go back to the idea from step 1 and try to use a property with the @Input() decorator, which will include the reference in the defined template instead of a flag:

I will not elaborate here on the ngTemplateOutlet directive or the used $implicit context property, the Angular documentation contains all the necessary information.

We can use our component with the default template:

We can also take advantage of the benefits of personalization and define one or both templates ourselves:

This is the solution you will most often find on blogs and in courses dedicated to Angular. Basically the solution is good – it allows us to define a default template, gives great flexibility in customization and allows us to encapsulate the logic of data processing inside the component. 

So if it’s good, why is it bad?

It is all about the usage. Let’s envision a view aggregating several components written this way. If every other will use customization, we’ll get many additional ng-template elements with different identifiers, which must not be duplicated. We also have to remember about passing the references to all the templates.

In an ideal world, I would like everything related to a component to be contained inside its tags and not to require any additional actions. The component should detect itself if and what templates have been defined and not require any additional parameters to be filled in. I would like the syntax from the second step, but with all the advantages described in the current step.

Step 4: Combining the advantages of the previous solutions

In order to perform the fusion of the previous steps, let’s consider what problems should be solved by the code we implement. First, we want to catch ng-template directives passed to our component directly between its tags. Secondly, we need to be able to distinguish these components, so they will need some kind of identifiers. What’s more – we need a way to render the final template where we combine the data processed inside the component with the template or templates passed from the outside.

At the beginning, let’s define our own directive that will help us read the template identifier:

Next, we use the @ContentChildren() decorator, which will give us a list of all the DOM tree elements nested inside our component. Iterating through this list, we will be able to check the identifiers of the passed templates – thanks to that, we can interpret their purpose properly:

In the listing above we used the ngAfterContentInit method. As it is known, the components in Angular have their own life cycle – from creation to destruction – which is described in detail in the documentation. Thanks to the so-called hook methods – one of which was used in the code above – we are able to hook into any of the stages of this cycle, which gives us control over when our code will be executed. 

Here and now we don’t want the iteration over this.templates array to be called before it is initialized. This property has been provided with ContentChildren decorator, so its value will be passed from outside the component ( elements passed between its tags). Therefore, any stage of the component lifecycle preceding AfterContentInit won’t be correct and will most probably end with an error when an attempt to execute it is made.

However, let’s go back to the implementation. The last step is to use the ngTemplateOutlet directive, which will combine the data processed in the component with the passed ng-template elements and generate the final template of our component. The complete code will look like this:

Example usage in default form:

Example use with template personalization:

Of course, nothing stands in the way of our component accepting only one ng-template element. Then, the identifiers would not be needed and the code would be a bit simpler.

The achieved result contains all the advantages of the previous step, while being more convenient to use. So – there we have it! ? ?

Then there’s the icing on the cake… the source code for the complete solution. https://stackblitz.com/edit/personalize-your-components

About the author

Łukasz Joorewicz

Lukasz has been working with Angular for years, but he is making his debut in writing articles. In his free time, he is a drummer in a rock band and recently became an avid cyclist. He likes to get away from his laptop from time to time.

Don’t miss anything! Subscribe to our newsletter. Stay up-to-date with the latest trends, tips, meetups, courses and be a part of a thriving community. The job market appreciates community members.

One comment

  1. Artur

    Thank you for this Great article and an interesting idea!
    Alternative approach, which imo is a little more friendly from the perspective of usage of your component api is via implementing dedicated structural directives like:
    AvatarDefinitionDirective, MainDefinitionDirective. This way it would be possible to strongly type template context, and save users of your component api from creating unneeded wrappers (….</…)

    // you can easily avoid passing user user to the directive, but this way you would be ably to strongly type template context. In you examples it is any I assume

    ….

Leave a Reply

Your email address will not be published. Required fields are marked *