You're reading for free via Tomas Trajan's Friend Link. Become a member to access the best of Medium.

Member-only story

The 6 Best Practices for building Custom Angular Components Library

Tomas Trajan
13 min readJul 20, 2021
System, have a system! (📷 by Dose Juice — 🎨 by Tomas Trajan)

UPDATE (10.08.2021) — Check out Angular Rocks podcast where Aliaksei Kuncevič and I discuss topics presented in this article!

Angular Rocks Episode about best practices for building of custom Angular component libraries!

The Experience

The ideas presented in this article are based on extensive experience from a large enterprise environment (150 Angular SPAs and 30+ libs ) — many of which consume our custom Angular component library!

🤫 Wanna know how we can manage such a large environment without our heads going 🤯

Check out Omniboard, the best tool for lead software engineers and architects that helps them to get an overview to drive change by querying and tracking all their code bases! 🛠

ps: I made it using Angular / Material & NgRx and it served as an inspiration for many of the articles you have read already 😉

☕☕☕☕☕: This article is going to be one of the longer ones so do not hesitate to jump straight to the section which interests you the most based on the issues you’re currently working on!

Table of Content

  1. Have a Plan & Definitely Have a Design System
  2. The Isolation of the Sub-Entries and All The Benefits it Brings!
  3. Complex Data is Always Passed in the Template Never Through @Input-s
  4. Translation Library Independence
  5. State Management Solution Independence
  6. Epic Showcase for Great Developer Experience

Have a Plan & Definitely Have a Design System

As with everything we do, we usually achieve better results when we’re prepared, at least a bit prepared, please! 😅

But then, the life happens, schedules are never perfect and we usually need to be flexible and deliver anyway…

That being said, it is of utmost importance to have a system, especially a design system when taking upon a task of creating a custom component framework for your enterprise organization!

As experienced Angular developers, we’re already most likely considering all the relevant things from module structure, through nice clean one way dependency graph to its impact on treeshake-ability, bundle size and performance but we often forget about STYLES!

We should always approach styling with the same level of seriousness and rigor as our overall library architecture!

Let’s get more hands on with a couple of examples to get a better idea of what this will mean in practice…

Component variants

It is pretty common that we start with a single button, card, checkbox… But as the time passes by, the UX designers realize that we need to introduce progressively more variants of these to be able to express desired flows as they envisioned.

For example, let’s imagine we start with two basic cards; raised and flat…

Example of basic raised and flat card

But then, two weeks after, we get a new use case which calls for a flat card on a grey background… How do we solve that? What about…

Border as a solution to having flat card on surface with the same background as the card itself

As the times go by, we might need to introduce some color variations. Maybe we need to differentiate some business flow with a new color…

Most likely, it’s already clear where this is going… Introducing the brand new…

The background is blue, I promise (at least on my display)

Ouch, this got out of hand pretty quickly, let’s take a step back and look what we have on our hands till this moment…

  • my-org-raised-card
  • my-org-flat card
  • my-org-flat-card has-border
  • my-org-flat-card has-border is-secondary

Now what about additional colors and combinations… What about multiple people (or even teams) introducing similar, but unfortunately never the same, additional modifiers for all the other components at the same time?

Houston, we have problem and its a very messy problem!

Wouldn't it be better if we had a system (or a framework) which would be able to express all the necessary variations from the start?

What we get with such system is a well defined “option space” where every possible variation of a component is known from the get go!

Option Space

Let’s imagine an example of such a system, for example, we can work with two dimensions:

  • component type — eg primary, secondary and tertiary
  • component theme — eg default, grey, blue, …

If we used them to create 3 x 3 table, we would see all the possible variations of given component!

This is very valuable even in case we do not plan to implement all of the variants right now (or even never)…

One of the best things about this approach is that it is extendable! We can easily keep adding additional types, themes or even whole dimensions! We will still end up inside of the system!

Let’s check out an example of how this could look like in practice…

Our example even includes the third dimension of “modifiers” like “is-large” which can apply on all the types / themes of the provided cards!

Now, how to implement this from technical point of view depends on specific choices we made in our custom Angular component library.

For example, do we need need to support IE11 (even though it was deprecated in Angular 12 yaass! ❤️) so we can’t use css variables… Do we implement every component as an Angular only or do we also have css only components…

As we see, it all depends but the system is a must!

Follow me on Twitter because you will get notified about new Angular blog posts and other cool frontend stuff!😉

Next up, let’s get more technical with library architecture and especially, sub-entries!

The Isolation of Sub-Entries

The treeshake-ability of the library represents a key concern when building custom Angular component library!

The library will be used for developing of a multitude of applications — be it internal or external — so it will have a performance impact for all those users, be it fellow employees or the customers!

Sub-entries are a way to make Angular library treeshake-able under all circumstances.

The out of the box treeshake-ability did in fact improve quite a lot with the advent of Angular IVY but there are still cases when sub-entries are necessary!

The in-depth dive is unfortunately out of scope for this article but worry not as I have your back anyway. The detailed step by step guide to implement Angular sub-entries was published earlier this year 😉

Besides the final bundle size of our consumers, the sub-entries will have a lot of positive impact on the architecture and implementation of the custom Angular component library itself, we’re going to get:

  • guarantee that we’re not introducing circular dependencies (the build would fail)
  • guarantee that we will have clean one way dependency graph between sub-entries themselves (prevent tangled ball of mud)
  • guarantee that our consumers are bundling least amount of code possible

⚠️ Now, before we venture further, a small heads up is in place!

The following section will have an impact on the other two which comes after it so it’s pretty important!

Complex data is always passed in the template using content projection (and never through the @Input)

When developing Angular components we are always encountering this one common question…

How do we pass and receive data in a child component to render some layout?

And usually we will go with one of the two main options:

  1. pass in data structure through @Input and the child will then render it
  2. use Angular content projection to pass simpler children into parent in the parent template

As always, there is no silver bullet and both approaches come with their own set of advantages and disadvantages, but as we will see, we’re going to get biased towards the second one as it has better overall properties for our use case of building re-usable component library!

Pass in a data structure

Our dropdown accepts its options using the @Input as a data structure which is then rendered internally

This first approach is chosen quite frequently because of a couple of advantages, at least from the perspective of the component library authors:

  • component acts as a black box, we have full control about how the options will be rendered
  • managing the state of internally rendered DOM is usually easier (for example highlighting an item in the list when selected or handling a click to select some item)
  • we can provide a well defined Typescript interface to enforce that the correct data structure has been passed into the dropdown

This sounds pretty good, right? So what are the disadvantages of this approach?

Most applications need some sort of I18n / translation support and our approach forces our consumer to do one of the two sub-optimal things:

  1. consumers have to pre-translate all the strings before passing data into the dropdown
  2. dropdown will translate keys internally — this will couple the dropdown implementation to a particular translation library implementation (which will then be forced onto all the consumers), also providing additional context for the internal translation becomes increasingly difficult
  3. application may deal with more than one type of translations, for example we might need to translate business codes next to plain I18n

As we can see, our internal implementation of the example dropdown is growing more and more complex and the above list of concerns could be for sure extended by other use-cases I haven’t encountered yet…

Let’s explore the tradeoff profile of the second solution…

Pass data using Angular Content Projection

Very basic example of using Angular content projection to pass items to the parent dropdown

The first example looks innocent enough so it doesn’t really give away its full potential, so let’s dig deeper!

Example of a more complex dropdown with the help of Angular content projection

Let’s unpack what’s going on in the bit more busy example above:

  1. we’re iterating over array of products *ngFor to render dropdown options per project
  2. content projection gives us flexibility to customize option item layout by projecting additional <my-org-product-type> (so we do not have to keep introducing more and more flags to the parent dropdown to cover all the use cases)
  3. consumers of the dropdown are free to use ANY pipe they wish, be it from I18n translation library or something totally custom

Our approach is obviously superior in terms of provided flexibility and developer experience from the point of view of the library consumers!

As we stated previously, everything comes with a cost and our approach is a bit more complicated to implement properly.

The backbone of our implementation will be the use of @ViewChildren in the dropdown itself to get hold of all the items to be able to handle the state and interaction.

This is further complicated by the fact that the items can be dynamically added and removed during the parent dropdown life time so we need a mechanism to re-register everything whenever they change…

Let me know in responses or on Twitter @tomastrajan if you would be interested in series about how to best implement each particular component!

Another example of flexibility provided by the Angular content projection when implementing components like a custom dropdown is that we can provide additional structure like groups and separators without any impact on the core mechanism of handling dropdown items themselves

🔑 The key takeaway here is that we’re prioritizing de-coupling of unrelated concepts and developer experience of the library consumers!

We do NOT want our dropdown to be aware of translations be it I18n or some other business pipe, more so we do not want it to be aware about the actual layout it is managing. Its only concern should be state and interaction of dropdown items!

Of course, the same principle can and should be applied also when implementing other components

Check out great article by Roman Sedov which gets into greater detail on how to do this in practice!

Translation Library Independence

The previous point can be then generalized into an approach which leads to a full translation library independence on the part of the Angular component library which is a desirable goal!

Some of the consumers in our organization may roll out custom translation implementation while others may reach for 3rd party solutions like ngx-translate, transloco or others…

To achieve such independence, we should always follow an approach that whenever we’re accepting some strings to be rendered by the library, it should be either direct template interpolation like <my-org-some-component>{{someProp | translate }}</my-org-some-component> or if we use @Input then it should be only for plain strings so then again, we can translate them using pipe like <my-org-some-component [label]="someLabel | translate"> .

Internal Translations

One question still stands, how do we manage translations of the text provided by the library itself? For example, things like a default placeholder or labels for various form input types like date picker names of days…

To handle this use case it would be the best to go with a simple private translation pipe to be used only by the library itself with some name like myOrgComponentsTranslationsInternal to prevent clashes with the consumer translation solutions.

We still have two options:

  1. pass language as a property to every component which needs it, eg <my-org-datepicker [language]="de"> — this would make the implementation simpler as we would not even need internal pipe, we could simple retrieve value from a plain TRANSLATIONS = { de: { 'some.key': 'Some translation '}} object, BUT the developer experience for consumers would be sub-optimal as they would need to provide language to every component that needs it
  2. implement some internal pipe which injects MyOrgLanguageService (provided by the component library) so that we can change language for all library components by calling setLanguage('de') on that global service — this solution still allows us to have fully independent translations in the consumer application, the only thing application needs to do is to set language at the start and whenever it changes

State Management Solution Independence

In the similar way, we also do not want to force any particular state management approach in the consumer applications.

And luckily, the previously described solution receives data through the template which represents a great place to decouple sourcing of the data from its usage!

Things we should always keep in mind

  • when implementing toast / notifications / global status popup — we should resist the urge to introduce global service and provide components instead, these components will then receive list of current messages to display and emit events based on interaction, the consumer apps (or higher level utility library) are going to handle these and integrate them with their own state management solution
Application can include global message in its core layout and feed it data, be it from NgRx store, plain Angular service or even component property
  • when implementing language selector — again we should resist expecting a specific state management solution even though it might be useful to have a global service as described in the previous point, the decoupled integration then can look something like this for plain Angular state management
State management independent integration of a custom Angular component library language service using plain Angular state management

… or the same using NgRx

State management independent integration of a custom Angular component library language service using NgRx state management library

🔑 The key takeaway here is that again that we’re prioritizing de-coupling and independence so that the consumer applications are free to do things their way!

Epic Showcase for great Developer Experience

And last but not least, we have to provide our consumers with a showcase with epic developer experience so they can get stuff done quickly and most important — without frustration — so they can deliver great applications for their users!

Every Angular custom component library showcase should have:

  1. examples how to use every implemented component including code examples, preferably the actual ones which are the example itself and even better with a support to copy it to the clipboard!

2. Simple menu with access to everything all the time, the search functionality can be a very nice bonus

PRO TIP: make your developers love you by auto navigating to a page if there is only one search match left 😉

Great, we have made it to the end! 🔥

I hope you enjoyed learning about the best practices and pro tips for building of your own custom enterprise grade Angular component libraries!

I would like to give special thanks to Kevin Kreuzer who is working with me on the project and was originator of many of these best practices! Go check out his articles and follow him on Twitter @kreuzercode!

Please support this guide with your 👏👏👏 to help it spread to a wider audience, it would be very appreciated 🙏.

If you work in a large enterprise organization with many teams and project then please don’t forget to check out Omniboard as it may really help you get an overview and drive change in your code bases and hence provide value for your organization and fellow colleagues!

Also, don’t hesitate to ping me if you have any questions using the article responses or Twitter DMs @tomastrajan.

Angular Experts — Supercharge your Angular application development with our expert support. Proven architecture, best practices and quality control ensure your team delivers an exceptional experience every time.
Welcome to the world of Angular excellence — angularexperts.ch

Do you find the information in this article useful? — We are providing tailored expert support for developing of your Angular applications. Explore our wide range offers on angularexperts.ch

And never forget, future is bright

Obviously the bright future! (📸 by Damir Babacic)

Wanna learn some Angular and you and your team are from Switzerland or surrounding countries? Check out the workshop offer!

In person & remote Angular workshops for you and your team!

If you made it this far, feel free to check out some of my other articles about Angular and frontend software development in general…

Tomas Trajan
Tomas Trajan

Written by Tomas Trajan

👋 I build, teach, write & speak about #Angular & #NgRx for enterprises 👨‍💻 Google Developer Expert #GDE 👨‍🏫 @AngularZurich meetup co-organizer

Responses (4)

Write a response

Thank you Thomas! Your articles on Angular libraries are like "the unofficial docs" on the subject! I've tried "deep-diving" into Angular Material's repo, but it seems like the usual guidelines aren't followed there.
Recently I've created an issue on…

Nice article, Tomas!
Are you able to explain in which cases that sub-entries are necessary?
I tried setting up a new project, exporting two modules from a library, and checking if the build of an app tree-shaked correctly when only importing one of…

Good points! And funny enough, we identified a very similiar set in our company. The only point where we differ is the decoupling of the translation solution. Instead of only allowing simple strings to be passed in, we provide an interface that the…