The Best Way To Lazy Load Angular Elements

Tomas Trajan
Aug 13, 2019 · 11 min read
Image for post
Image for post

UPDATE: Check out video presentation from Angular Zurich Meetup & the library itself!

Or any other Web Components in your Angular applications!

In this article we will be focusing on learning how to use Angular elements and other web components in the context of parent ( consumer ) Angular applications. It’s based on hands-on production ready experience from large enterprise organization with more than hundred of SPAs and libraries combined!

Angular Elements and Web Components Refresher

Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps — MDN

For many years we have been building websites and simple web applications using HTML elements provided by the platform like <div> or <input/> and vanilla Javascript and it was good!

As the applications grew in complexity, community came up with web frameworks and libraries that introduced the concept of components.

A component represents a great way to implement and encapsulate complex behavior which then can be reused in many places of our applications

Consider the difference between <p> and <todo-item>.

A paragraph can display text with some style and maybe some animations if we get a bit fancy with the css transitions.

Compared to that, a todo item can in theory do anything from simple displaying of a todo text to a rich interaction like:

  • toggling
  • assigning to an user
  • tagging
  • setting reminders and deadlines

The possibilities are literally endless…

Sounds good right, so what’s the catch?

Every web framework and library uses its own internal implementation of components which almost as a rule is NOT compatible and hence can’t be reused in applications built by other frameworks or libraries…

Solution to this problem represents one of the largest potential benefits of using web components when building web applications. It enables us to implement components once and use them in many apps regardless of the chosen technology…

Web components API is closely related to the native HTML elements and the DOM itself which is a very vanilla like experience…

As with the original web apps, soon we will find ourselves in a situation where we could really use the help of some framework or library when implementing more complex behavior of our web components…

Wouldn’t it be great if we could use familiar and powerful tools like Angular to create components and then somehow transform them into the web components and use them happily ever after?

This is exactly the purpose of Angular elements!

The library uses createCustomElement() function which takes Angular component and wraps it to behave just like standard HTML custom element.

Image for post
Image for post
Using Angular elements createCustomElement function to wrap our component and make it available as a custom element.

Use cases

Cool, now we know what web components are and how Angular does help us to create them in a more developer friendly fashion. Let’s explore situations in which they can be useful!

Imagine a large enterprise organization building many standalone frontend applications which look exactly the same but handle only single aspect of business requirements. A user will seamlessly switch between apps using standard HTML hyperlinks without noticing anything special. From users perspective it behaves as a single application with lots of features.

Let’s say many of these applications will share some piece of functionality, for example a “tasks widget”. The widget will simply let employees know what they should be working on as a next task…

This kind of widget will be pretty isolated in terms of its model (data) and its interaction with the rest of the application. The parent application will most likely just pass in some context and react to widget emitted events…

Image for post
Image for post
Multiple independent applications all using the same widget

In real life scenario, many of these applications will be developed by independent teams and have independent release cycles.

This means that if we implement our widget as a standard Angular library and release a new version (for example 3.1.0) we would have to wait for new releases (and deployments) of every consumer application before we could be sure that our widget lib made it fully to the production…

Image for post
Image for post

Because of this we can often face situation when we are running different versions of the same widget in various apps which may cause other problems related to breaking changes with relation to backend APIs, incompatible data, or myriad other things…

Wouldn’t it be great if we could deploy new version of our widget independently from release cycle of consumer applications?

This is exactly what is enabled by the use of Angular elements! The “sub-application” element is released and deployed independently as a single file bundle which is then referenced ONLY by the url in the consumer apps.

To make referencing easier, the bundle file name can always stay the same (for example some-element.js without any hash) for every version of the element. The cache busting is then handled with the help of etag which is transparently calculated based on the file content on the server. Learn more about ETag HTTP response headers.

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

Let’s say we are building an Angular application and we are paying extra attention to architecture of the features making them nicely isolated and lazy loaded. The performance, maintainability and start up time of our application is great!

Then there is this new requirement to add possibility to inline edit every item of a large list using a powerful (but also very heavy) rich text editor.

As a result the size and hence loading of that feature will suffer even though the editor is only displayed as a result of user interaction and never from the start.

Now to be fair, Angular does support lazy loading of the components but it is not such a great developer experience just yet…

On the other hand granular lazy loading of the Angular elements can be extremely easy!

Cool, we have seen that the elements could really help us in some of the common situations which can be encountered in most enterprise organizations…

Building and serving Angular elements

As teased in section above, Angular elements is basically an Angular application which uses createCustomElement() function from the @angular/elements package to wrap standard Angular components and make them available as native custom elements…

This means we can leverage Angular CLI to build our elements projects the same way as any other Angular application!

But hey, Angular CLI produces quite a number of bundle files which doesn’t sound too practical for referencing them in the consumer apps…

This is a real concern and it can be addressed by using amazing ngx-build-plus library by Manfred Steyer. It enable us to use --single-bundle flag when running ng build command and as suggested by its name, it produces a single bundle file!

Also, we will be using --outputHashing none flag to keep the bundle file name the same! This is because we want consumers to be able to reference our element without the need to update that reference for every new release of the element.

Putting hash in the element file name would defeat the original purpose of getting independent release cycles!

This leaves us with two main options. Either we use very short HTTP cache expiry times or go with the ETags depending on the use case and the expected release cadence…

The elements bundle then can be served by any HTTP server or CDN!

How to consume elements in our Angular applications

Good! Our element is out there somewhere, available on some url so the only thing left is to use it in our application!

Being a Javascript bundle, the most straight forward way to consume an element would be to simply include <script> tag in the <head> of our page to make sure that the element was downloaded and registered before the first occurrence of its tag in the <body>

Image for post
Image for post

This might make sense for elements that we want to use straight from the get go but usually it is better to postpone loading of the element bundle until the last possible moment before it is used!

Now, we said we’re focusing on consuming elements in the context of parent Angular applications…

This means that we have all the great Angular features at our disposal, one of them being structural directives!

Structural directives are directives prefixed with the * character, the most famous one being *ngIf.

The * is “syntactic sugar” for something a bit more verbose. It changes the underlying page structure by wrapping of the host element with the <ng-template> element effectively removing it from the DOM.

This is very helpful because it enables us to implement mechanism that removes element tag from the DOM and only adds it back after we loaded the element bundle!

  1. remove element tag from the DOM
  2. add <script> tag with elements bundle url to the body
  3. wait for the bundle to be loaded
  4. add element back to the DOM

The good news is such a directive already exists!

Image for post
Image for post

Introducing axLazyElement directive, the easiest way to lazy load Angular element or any other web component in your Angular application!

  1. showed some loader while downloading element bundle
  2. showed some use friendly error message if loading fails
  3. made sure that the element bundle was loaded only once
  4. supported both single file bundles and Javascript modules which are supported by the modern browsers…
Image for post
Image for post
Example of using loading and error templates together with axLazyElement directive, also note that the standard Angular property and event binding works as expected!

It is worth noting that use of such directive will postpone loading of the elements bundle until the application tried to render its <some-element> tag for the very first time.

This means that if we wrapped our element tag with another *ngIf directive we could postpone rendering and hence loading of the element bundle until the condition in *ngIf resolves to true as in the following example…

Image for post
Image for post
The element will be loaded only as a result of user interaction which triggers rendering of the element tag in the component template!

Imagine we might need to use given element in multiple features or even multiple time in a single component. In both cases, only the first ever occurrence of the element tag will trigger download of the bundle!

This is implemented using centralized elements registry which compares them based on the url so one url is guaranteed to ever only be downloaded once!

Image for post
Image for post
@angular-extensions/elements library

Learn more about the *axLazyElement directive and the parent library called @angular-extensions/element !

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

Experience from production and lessons learned

As with everything in life, nothing is completely perfect! It is always worth exploring if given solution and its trade-offs does make sense for your particular use case…

Angular elements represent one of the best candidates for zone-less approach. They are usually smaller in scope and isolated by definition.

If you are going to implement new Angular elements from scratch I strongly recommend you to consider going zone-less!

This approach might not be the best when converting existing Angular implementation to elements. We do not want to adjust entire state management and live with the fear that we forgot to manually detect changes in that one missing stream😬!

In that case we can still use zone.js in our elements, BUT there are couple of points to be aware off…

  • element ngOnChanges triggered by the input from parent Angular component will run in parent zone and hence not update element view…
  • rxjs might run in a wrong zone, partial solution is importing zone.js/dist/zone-patch-rxjs but this will work only for the first bundle, every other import will be ignored as it is tracked by global zone so this will not work when using more than one element…
  • the most robust even though not the nicest solution is to reuse parent ngZone by storing it on the window and pass it to the element during its bootstrap, that way everything runs in the same zone and change detection works just as expected!
Image for post
Image for post
Pass parent Angular app ngZone to the element using window
  • and one more thing, be careful with using ngZone.runOutsideAngular as it can pull the whole rxjs stream outside of Angular zone and run in the <root> zone instead!

Angular CLI enables us to have multiple projects in a single workspace. This can help us develop our element with pretty great workflow which involves workspace with two SPAs — element and the demo!

The main benefit of this approach is we can see element running not just as a plain Angular application ( which we could have got using ng serve ) but we can also consume it as a real element in the demo SPA by running them both on different ports simultaneously (eg 4200 and 4201).

That way we can consume element in its final form and try out all the inputs and outputs without the need to integrate it into the final consumer!

We can make this work by introducing a bit of custom Angular CLI proxy configuration documented here with the goal to be able to retrieve element bundle in the demo without the need to hard-code the port into the url!

Image for post
Image for post
Example of using Angular CLI with newly created proxy.json file to rewrite requests for /demo/elements.main.js to http://localhost:4201/main.js

Yep, we made it to the end! 👍

I hope that you enjoyed this article and learned more about Angular elements, web components and the best way of using them in your Angular applications! Check out the @angular-extensions/elements library and use it to the fullest!

Please support this article with your 👏👏👏 because it helps it to spread to a wider audience 🙏.

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

And never forget, future is bright

Image for post
Image for post
Obviously the bright future (📷 by Mohamed Masaau)

🤫 Psst! Do you think that NgRx or Redux are overkill for your needs? Looking for something simpler? Check out @angular-extensions/model library!

Image for post
Image for post
Try out @angular-extensions/model library! Check out docs & Github

Starting an Angular project? Check out Angular NgRx Material Starter!

Image for post
Image for post
Angular NgRx Material Starter with built in best practices, theming and much more!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store