Our content is free thanks to ag-Grid

ag-Grid is the industry leading JavaScript datagrid

ag-grid.com

Lazy loading Angular components from Non-Angular applications

Post Editor

How to lazy load Angular components from a Non-Angular project. Ideal for dynamic websites using CMS platforms.

7 min read
post

Lazy loading Angular components from Non-Angular applications

How to lazy load Angular components from a Non-Angular project. Ideal for dynamic websites using CMS platforms.

post
post
7 min read
7 min read

Introduction

I have been working with Adobe Experience Manager (AEM) for almost two years and most of the time I combined it with Angular as a frontend framework. One of my first tasks as an AEM developer was to find a good way to use Angular in conjunction with the CMS platform.

At the time, Angular 6 had just been released and one of its main new features was Angular Elements. Angular Elements allows to create Web Components from Angular components and reuse them in other applications using different technologies. Since Web Components are framework agnostic and self-bootstrapped they are great for dynamic sites with CMS platforms.

There are some articles you can read about Angular Elements:


Building

One of the good things of Web Components and custom elements is that you import some JS and CSS and you are ready to use them. With Angular you can run ng build --prod and import the generated bundle files in other applications to use your custom elements.

Also you can use ngx-build-plus to build your custom elements. With this library you can get a single bundle file when building your project with Angular CLI.


The problem

Despite there are use cases where it could be handy to have all the components bundled to a single or a few files -like design systems-, there are other cases where it is not ideal.

In my particular case I have an Angular project with about 20 -big- components that are included as custom elements in a dynamic site powered by Adobe Experience Manager. But, only one or two of those components are included in each page.

So, if only one of the components is used within a page I would be delivering a lot of unnecessary JavaScript to the browser, leading to slower loading times.


Lazy loading

Code splitting and lazy loading would help to tackle that problem. You can split your application into multiple NgModules accordingly.

In my case, I could split up my project by creating a separate NgModule for each of my components and one or more shared modules to share features across the whole project. Now I would only need to lazy load them in order to lazy load my components.

There are several options to lazy load components in Angular, for example:

But, how to lazy load the components from Non-Angular applications?


ngx-element

With ngx-element you can lazy load your Angular components from everywhere. That means from a CMS platform, a React application or just a plain HTML.

The library will define a custom element which you can pass a selector attribute to. That selector attribute determines what component you want to load. Also you can pass in attributes to your component by setting data-attributes to the custom element.


Usage

Let’s create a small Angular application to see ngx-element in action :) I am using Angular CLI v9.0.6. Choose SCSS as the css preprocessor.

$ ng new lazy-components --minimal
$ cd lazy-components

We can remove app.component.ts since we won’t need it and modify app.module.ts accordingly.

After doing that our app.module.ts file should look like this:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

@NgModule({
  declarations: [],
  imports: [
    BrowserModule
  ],
  providers: []
})
export class AppModule {
  ngDoBootstrap() {}
}

Basically I removed the App component and added the ngDoBootstrap method since we are not bootsrapping any component in the module.

Now let’s create a Talk component together with its feature module.

$ ng g module talk
$ ng g component talk

At the moment you should have the following folder structure:

Folder structure with a feature module and a component.

And your talk files should look as follow:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TalkComponent } from './talk.component';

@NgModule({
  declarations: [TalkComponent],
  imports: [
    CommonModule
  ]
})
export class TalkModule { }

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-talk',
  template: `
    <p>
      talk works!
    </p>
  `,
  styles: []
})
export class TalkComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Let’s change our Talk component to make it display some information about a talk in a conference and give it some styles.

Update the talk.component.ts file to the following:

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-talk',
  templateUrl: './talk.component.html',
  styleUrls: ['./talk.component.scss']
})
export class TalkComponent implements OnInit {

  @Input() title: string;
  @Input() description: string;
  @Input() speaker: string;
  @Input() tags: string;

  talkTags: string[];

  constructor() { }

  ngOnInit() {
    this.talkTags = this.tags ? this.tags.split(',') : [];
  }

}

And create the following talk.component.html and talk.component.scss files next to talk.component.ts:

<div class="talk">
    <h2 class="title">{{ title }}</h2>
    <p class="description">{{ description }}</p>

    <div class="foot">
        <div class="tags">
            <span class="badge" *ngFor="let tag of talkTags">{{ tag }}</span>
        </div>
        <p class="speaker">{{ speaker }}</p>
    </div>
</div>
.talk {
    background-color: #f0f0f0;
    margin-top: 20px;
    padding: 10px 20px;
    box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
    transition: box-shadow .3s ease;

    &:hover {
        box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
    }

    .title {
        margin-bottom: 0;
    }

    .description {
        margin-top: 0;
    }

    .foot {
        display: flex;
        justify-content: space-between;
        margin-top: 20px;

        .speaker {
            margin: 5px 0;
        }

        .badge:first-child {
            margin-left: 0;
        }
    }
}

Until now we have created a component that (trust me) will look like this later:

So nothing strange until now, right? We have created a typical Angular application with an AppModule, a feature module and one component.

Remember that our goal is to use this component in Non-Angular applications and be able to lazy load it. We need Angular Elements and ngx-element in order to do that, so let’s put them in action…

Install Angular Elements

Angular provides a schematic to install and set up Angular Elements in our project. It will add a polyfill but it does not support IE11. If you need IE11 don’t use this schematic and see this article instead.

$ ng add @angular/elements

Install ngx-element

$ npm install ngx-element --save

Expose the Talk component for ngx-element

In order to let ngx-element to access our component and create it on demand we need to make a couple of additions to our talk.module.ts.

First we need to add TalkComponent to the entryComponents array. And second we are going to add a customElementComponent property to the module in order to make the component’s class accessible to ngx-element.

Our talk.module.ts should be like this now:

import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TalkComponent } from './talk.component';

@NgModule({
  declarations: [TalkComponent],
  imports: [
    CommonModule
  ],
  entryComponents: [TalkComponent]
})
export class TalkModule {
  customElementComponent: Type<any> = TalkComponent;
}

Once we have done this we need to import and configure the NgxElementModule in our AppModule as follows:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { NgxElementModule } from 'ngx-element';

const lazyConfig = [
  {
    selector: 'talk',
    loadChildren: () => import('./talk/talk.module').then(m => m.TalkModule)
  }
];

@NgModule({
  declarations: [],
  imports: [
    BrowserModule,
    NgxElementModule.forRoot(lazyConfig)
  ],
  providers: []
})
export class AppModule {
  ngDoBootstrap() {}
}

Let’s test our component! ?

In order to test our component we are going to create some HTML where we can use it. Remember that we are not bootstrapping any Angular component and we are just adding custom elements to the DOM.

Replace the index.html file in the project with the following markup:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>LazyComponents</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
  </head>
  <body>
    <div class="content">
      <h1 class="section-title">Talks</h1>
      <div class="talks">
        <ngx-element
          selector="talk"
          data-title="Angular Elements"
          data-description="How to write Angular and get Web Components"
          data-speaker="Bruno"
          data-tags="Angular,Elements"></ngx-element>

        <ngx-element
          selector="talk"
          data-title="Lazy loading"
          data-description="How to lazy load Angular components in non-angular applications"
          data-speaker="Somebody else"
          data-tags="Angular,Lazy loading,Components"></ngx-element>
      </div>
    </div>
  </body>
</html>

And replace the global styles.scss file with:

@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');

body {
    font-family: 'Open Sans';

    .content {
        max-width: 1000px;
        margin: auto;

        .section-title {
            margin-top: 60px;
        }

        .sponsors {
            display: flex;
            flex-wrap: wrap;

            sponsor-element {
                margin: 0 20px;
            
                &:first-child {
                    margin-left: 0;
                }
            }
        }
    }
}

.badge {
    margin: 0 5px;
    padding: 5px 10px;
    background-color: #000099;
    color: white;
    border-radius: 15px;
    font-size: 11px;
}

Run it! ?

At this point, if you run ng serve you should see our component in action:

And you can see that our Talk Module is being lazy loaded as we expected.


Play with it

Now you can open your DevTools in the Network tab and verify that our TalkModule is being lazy loaded.

Some things you can play with to see the powers of custom elements:

  • Add a new talk to the DOM and see how it is self-bootstrapped.
  • Change the title , description and speaker attributes from the DevTools.
  • Remove the talk custom elements from index.html file and verify that the TalkModule is not loaded initially. Then add a talk element to the DOM on the fly from the DevTools and verify that the TalkModule is lazy loaded.

Conclusion

With ngx-element we have built a component and leveraged all benefits of Angular framework, custom elements and lazy loading.

This library has changed the way I integrate Angular and Adobe Experience Manager for the better. I hope this can be useful for developers trying to use Angular as a frontend framework together with CMS platforms or any other Non-Angular projects.

Thank you for reading ?

Discuss with community

Share

About the author

author_image
Bruno Bradach

Uruguayan frontend developer @ Conexio

author_image

About the author

Bruno Bradach

Uruguayan frontend developer @ Conexio

About the author

author_image
Bruno Bradach

Uruguayan frontend developer @ Conexio

NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

RxJSpost
21 January 20214 min read
RxJS in Angular: Part III

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

RxJSpost
21 January 20214 min read
RxJS in Angular: Part III

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

Read more
RxJSpostRxJS in Angular: Part III

21 January 2021

4 min read

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

Read more
Angularpost
20 January 20216 min read
Angular and SOLID principles

In software engineering, making things work the first time is always easy. But, what if you want to add new functionalities to an existing code? Making iterations on an existing basis can be difficult to do without introducing bugs. This is where SOLID principles come into play.

Angularpost
20 January 20216 min read
Angular and SOLID principles

In software engineering, making things work the first time is always easy. But, what if you want to add new functionalities to an existing code? Making iterations on an existing basis can be difficult to do without introducing bugs. This is where SOLID principles come into play.

Read more
AngularpostAngular and SOLID principles

20 January 2021

6 min read

In software engineering, making things work the first time is always easy. But, what if you want to add new functionalities to an existing code? Making iterations on an existing basis can be difficult to do without introducing bugs. This is where SOLID principles come into play.

Read more
Angularpost
14 January 20216 min read
Demystifying Taiga UI root component: portals pattern in Angular

Just before new year we announced our new Angular UI kit library Taiga UI. If you go through Getting started steps, you will see that you need to wrap your app with the tui-root component. Let's see what it does and explore what portals are and how and why we use them.