Angular 19 & ImportMaps

How to create an Angular 19 Custom Element with the usage of ImportMap

Liferay with Angular Custom Elements

Many weeks ago, David H Nebinger show us how to use React Custom Element and optimize them with ImportMaps.

I ask him how to do this with Angular and he answer me that it were not a Liferay issue but an Angular one.

And he is right but how to do that with Angular and why not with Angular 19.

To create an Custom Element, simply create a traditional JavaScript application that will declare one or more HTML "Custom Elements.".
In Angular, we'll do this declaration as follows: In the app.module.js file created by default, I'll declare my "Custom Element," which I will name "sample-custom-element" as follows:


            ...
            @NgModule({
                imports: [BrowserModule],
                providers: [],
            })
            export class AppModule {
                constructor(private injector: Injector) {}
        
                ngDoBootstrap() {
                    const AppComponentElement = createCustomElement(AppComponent, {
                    injector: this.injector,
                });
        
                customElements.define(
                    'sample-custom-element',
                    AppComponentElement
                );
            }

Then I will declare this new HTML element in my “index.html” file, for the example I will put it in the body:

        
            ...
            <body>
                <sample-custom-element></sample-custom-element>
            </body>
    

When compiling my module, Angular will automatically transform my index.html to integrate the files necessary for its execution (the famous scripts.js, runtime.js, polyfills.js, etc.). Example with a "Custom Element" in Angular 19:
 



After that, if I want to use ImportMaps in my Angular project, I will add this:



 

Here, all the modules have been externalized into an "importmap" script tag in the head of the page. This data will no longer be included in the deliverables and will be shared between all Angular 19 Custom Elements.

For this to work, I admit that even AI (Copilot in my case) wasn't much help, and I'd love to hear if anyone can compile in AoT mode instead of JiT.

The full source code is available on my GitHub repo: Link to my GitHub repo

FYI, you'll need to disable AoT mode in the "angular.json" file for this to work... it would be too easy otherwise 😉!
The idea is to change the compilation mode to "esbuild" mode and declare the desired modules as external dependencies:
 


        "externalDependencies": [ 
            "@angular/animations", 
            "@angular/common", 
            "@angular/compiler", 
            "@angular/core", 
            "@angular/elements", 
            "@angular/forms", 
            "@angular/platform-browser", 
            "@angular/platform-browser-dynamic", 
            "@angular/router", 
            "rxjs", 
            "tslib", 
            "zone.js"
        ]

This way, Angular will understand that they shouldn't be included when creating deliverables. You'll also need to modify the "tsconfig.json" file to switch it to "esnext" mode.  

And Finally, I will get this :



Here, no Liferay... only an Angular 19 app with the usage of the ImportMaps.

After, I can integrate my Custom Element in my Liferay and declare the ImportMap directly in Liferay (because no index.html in this case !)

Here is what I get for exemple if I put 2 Custom Elements in the same page : 



This post is a special "Liferay" article based from one of my posts but more generic on micro-frontends here: https://niji.tech/micro-frontends-how-to-use-them
It is in French but you can ask to your browser to translate it easily.

Thanks to David H Nebinger for the inspiration.

Blogs