Angular Components, Templating, and Reusability
If you’ve been using Angular for a while now then you probably already know some of these points I’ll be mentioning here. However, if you’re new to the tech, and you want to develop the right coding habits to be really awesome in it, then you better read on.
Component reusability, its limits, and the usual practices
Coding components with reusability in mind is not only an ideal practice, but it’s actually practical and will make you more efficient. How exactly?
Well, when creating a component, we often end up with a situation like this one:
In that kind of situation, we end up with a component that is very hard to extend and with a ton of parameters. Not customizable at all.
The customization are done by adding more booleans. And when the day comes that we’ll want to change how the rows are displayed, we’ll have to create completely new and different components such as app-user-list
and app-blog-list
, which will duplicate a lot of code.
Components are great… when they are simple to use. But often, they come with a fixed template that can’t be changed. So let’s discuss how to improve this kind of situation.
Content Projection (transclusion), TemplateRef, and Containers
Angular comes with a few ways to inject content in the DOM. The first one is content projection, which is better explained with a simple example:
Pretty simple, the content of the component tag is projected inside the component and replaces the ng-content
tag. The problem that comes with this is if you pass a sub-component in the content that sub-component will be built in the parent component. Consequently, it will not be destroyed when the child component is destroyed.
This can cause a memory leak:
Here, app-my-other-component
is built not in app-my-content
but in the component calling it. Therefore, it will not be destroyed when app-my-content
is destroyed.
So, content projection is great for decorating simple HTML code, but that stops being true when you start injecting components.
Template to the rescue
It’s easy to fix that problem though, we simply need to use a template:
What have we done in the example above? We created a template using ng-template
and injected it as a parameter in the child component. The child component is then using it in a container, thanks to the ngTemplateOutlet
structural directive.
By doing so, we avoid the memory leak as the ng-template
will be created only when it’s used inside the child component.
ng-container vs ng-template
In the example above, we also introduced the ng-container
tag. The ng-container
is simply an element used to group other elements without interfering with the DOM. A structural directive (ngIf, ngFor, ngSwitch, etc.) can also be attached to that element.
ng-template
will create a TemplateRef object containing the elements grouped in it. It will not affect the DOM until it is actually used.
ViewChild vs ContentChild
In the previous example, we still use component parameters to inject into our template. But if we have many templates, we’ll end up having the same problem we initially wanted to solve. So let’s make a few changes to our code, shall we?
Here, we introduce the ContentChild decorator. Maybe you already know the ViewChild decorator and you’re thinking that it’s working pretty much the same. However, the ViewChild will find a child in the component, defined by it’s template while the ContentChild will find a child in the content defined by the user of the component.
By doing that small change, we avoid passing many parameters to the component, and the code gets even clearer.
Example of a Page Component
Below, we are going to create something more practical. The component will be a carded page, displaying a list of cards, a title, some actions, a filtering and sorting system, and a pagination.
I greatly simplified the HTML code here to make sure it’s still clear.
As you can see, it’s very easy to customize our page because we have access to every placeholder in the child component. If we want to display or hide a button, we can simply add a ngIf
in the template or just remove it. If we prefer to have an infinite scroll instead of a pagination, we can simply remove the pagination and customize our list accordingly.
The PageCardComponent
will still define most of the CSS and HTML to display the page properly.
Styles Scoping
In Angular, all styles are strictly scoped to the component in which they are declared. This means you cannot define “global” styles reusable by all your ContentChild elements unless you specify ViewEncapsulation.None
in the child component’s decorator. That will make the style in that component global to the whole application. So it's good practice to manually add some scope in the CSS file.
Some quick tips for better reusable components
Push the services up the tree.
Data querying should not be in child components since that would create a coupling between the component and the service. This is so we don’t hit any snags if we want to reuse that same component but with different data.
Listen to events instead of injecting services
Whenever possible, it’s better to have the services in the parent component and simply listen to events from the child components. This is not always possible though, for example, when we want to display error messages or logs, or maybe open a modal window. But since those couplings are not too tight as these services don’t provide data, then you can simply let those slide.
Clean-up the parameters
When you start having too many parameters, think about templating. This is especially advisable when some of the parameters are just toggles for displaying or hiding elements.
The Roadmap
Here’s a quick recap of everything I’ve discussed here, so far.
- Component reusability, limits, and usual practices
- Passing input to the components
- Less flexibility, too many parameters
- When to switch to templating
2. ng-content vs ng-template vs ng-container
- ng-content is used for content projection
- ng-container provide us with an element to which we can attach structural directives without adding any element in the DOM
- ng-template create a templateRef object that can be used
3. Transclusion vs Content projection
- Transclusion mean passing the template to the component, the template will be duplicated but the component will have more flexibility and will be easier to use
4. ViewChild and ContentChild, their differences and how to use them
- ViewChildren are provided by the component
- ContentChildren are provided by the user
5. What is a structural directive?
6. templateRef and ngTemplateOutlet
7. Styles scoping
8. Component and template identification
Helpful tips during the design phase
Lastly, you can avoid a lot of frustrated hair-pulling and heartaches if you follow these tips right at the start of a project, especially during the design phase.
- Define the type of pages you have (list, form, dashboard, etc.)
- Define the different emplacement you want to customize in every page. It can be the menu, the topbar, the header, etc.
- Define elements that are kept and cannot be customized
- Identify and define common pages/components
- Identify and define customizable/reusable parts in the pages/components
- Clearly separate and mark the customizable parts