The cover image is from Rembrandt's drawing "Belshazzar’s Feast". It shows a scene from the Old Testament. The writing on the wall says “You have been weighed on the scales and found wanting”.
Although a little bit exaggerated, I found it as good metaphor for the current reputation of web components.
Web Components do not deliver on their promises. I wrote this essay for Angular developers who are evaluating web components or are wondering what went wrong with the web components they already use. I will explain the reasons web components fail and pinpoint the few application types where web components make sense to use.
If you visited a conference on Angular in the last two years, chances are quite high that you attended a talk about web components (WC).
Many frameworks can translate their representation of components to WCs. In Angular Elements, for example, you write just two lines of code to transform a component or an entire app into a web component.
Isn’t that paradise on earth? Well…, no. Quite the opposite.
WCs are independent from each other. Still, they must communicate with each other.
That's why you need to come up with a so-called app shell. This is a web application on its own which wraps and orchestrates your WCs.
Let's take two typical tasks of such an app shell.
Depending on the change of the route, the app shell hides or shows certain WCs. This is something that each router library does out of the box.
The shell listens for events relevant to the other WCs. Each time a web component fires such an event, the shell passes it on to the others. As it turns out, ngrx does that quite well.
The problem is, existing routing or state management/messaging libraries do not work with WCs. They are designed in a way where they need more direct communication with each component. WCs run in isolation. There is simply no way to access their underlying components.
Of course, you can use both libraries in your app shell. But remember that you have to write a full application that is pure overhead. Be prepared for some pioneering work.
Software is built upon assumptions, many of them common sense. Frameworks, for example, take it for granted that they only have a single instance on a web page.
Such assumptions reduce the complexity of the framework's code. As a consequence, a framework can modify certain global resources like the URL or the DOM and "bend" them to its needs. After all, the framework acts as a gatekeeper and gives the embedded application indirect access to them.
Let me give you two examples for a better understanding.
In Angular, the way you interact with the URL is via the router library. You would never interact with the window`s history object. So Angular expects that only it has access to, and can manipulate, the URL. As soon as you use two Angular WC applications, each with its own router, you have a problem. In all likelihood, the routing will not work anymore. See this Github issue for more details.
Another example is zone.js. This is one of Angular's core parts. It patches the DOM so that Angular is aware of any events that take place and can do its change detection. Basically, just internal stuff you don't have to care for.
Well, guess what? Just as with the router library, Zone.js thinks it is unique. It might work. It might not work at all. Or you might get the worst case: It works most of the time in development mode but it will rain fire and brimstone in production. You can learn more from Michael Hladky’s talk.
WCs revert the framework's self-conception of being the only player on the field. Each piece of code that is based on that assumption puts the whole application at risk.
You will not be able to fix that. It is fundamental to the framework. You can try to find a workaround, let go of that particular functionality or, even worse, implement it on your own.
It goes without saying that this does not apply only to Angular. Other frameworks or libraries have the same problem.
Another major problem with WCs is the bundle size. You might not see it as that problematic with Intranet web sites.
There are some improvements in development. For example Angular's internal renderer Ivy may reduce the bundle size significantly.
Either way, none of these improvements will remove the bundle size issue completely. So you have to accept that your WCs will impose quite a huge overhead. The more you use it, the more that overhead grows. This can turn out to be a real blocker.
Another common use case for WCs are shared widgets. These are lightweight components that don't require heavy armoury like ngrx or routing. Just think of something like Material or Bootstrap whose fields and layout elements provide a common look and feel.
The WCs are not the problem with that approach. It is Angular itself. Packed with a lot of features, Angular is a framework for enterprise applications that requires a steep learning curve. The investment pays off when you create big enterprise-sized applications.
But a heavy system like Angular is overkill for small visual elements. Pick the right tool for the job. Consider something lighter like Stencil which has been made for WCs.
But the concept of microservices is a poor paradigm for frontends. Microservices run in sophisticated environments that guarantee isolation and provide a complete tool set for orchestration. You very likely heard of Kubernetes and its powerful ecosystem as well as supporting infrastructure like AWS or Azure. Now take a step back and compare that to a browser…. We don't have anything comparable to Docker in the browser. Don't even dream of something like Kubernetes or beyond.
That is a completely different world. It is just unrealistic to even think one can simply take the concept of microservices and apply it to the frontend world.
You hear many stories of companies that successfully adopted WCs. Don't fall into the trap of blindly applying each of their actions to your company.
Those companies are not big and successful because they use WCs. They use WCs because their size demands it. Their success and growth came before their adoption of WCs.
It is like a website with 5 visits per day which builds a data center because they want to have as much traffic as Facebook.
By now, you know all the bad things about WCs ;). Is there anything positive at all? Yes, of course. At the end of the day, when you run out of all other options, WCs are the ones that are left. And then you are grateful to have them at your disposal.
Here are two possible use cases perfectly fit for WCs.
Think of yourself as a company with more than 100 developers working on the same application. You can't coordinate that anymore centrally. In that scenario, it makes perfect sense to use WCs. Additionally, you would have the resources to afford a dedicated WCs team that provides support for all upcoming challenges.
Another possibility is that you want to migrate an AngularJs application. You could start out with embedding the first parts of Angular as a web component. But this is just a temporary solution. You will only use WCs in the beginning. As soon as the Angular part is large enough it should break out of its "WC cage" and take over.
At the time of this writing, an interesting alternative to Microfrontends with WC is in development. It is based on an upcoming feature in Webpack 5 and is called Module Federation.
There are sparse but valid reasons for web components. If you choose to go with WCs, prepare for many workarounds and trade-offs.
Most of the time though, you just don't need web components.
- MDN: Web Components
- Angular Elements
- Michael Hladky: Angular Elements and Zone-Less components in production
- Stack Overflow: Angular elements with Angular routing
- Timon Grassl: How to use Routing in Angular Web Components
- Github Angular: Routing in Angular Based Web Components using @angular/elements
- ngVikings 2020, Manfred Steyer: The Microfrontend Revolution - Module Federation with Angular
- Manfred Steyer: The Microfrontend Revolution - Module Federation in Webpack 5