Generating dynamically loaded components in Angular 2 with lazy loading

We are integrating an angular2 app into a cms (Sitecore) where content editors need the ability to add, remove, and rearrange components on a page, as well as include new components as needed.

This is achieved by having the cms generate script tags to load our components. An example of the generated html is:

<body>
    <sales-root></sales-root>
    <script type="text/html" id="generated-header">
        <sales-welcome></sales-welcome>
    </script>
    <script type="text/html" id="generated-content">
        <sales-personal-info></sales-personal-info>
        <div><!-- regular html content --></div>
    </script>
</body>

Within the sales-root component, we have:

export class AppComponent extends Translation implements OnInit {
    @ViewChild('header', { read: ViewContainerRef }) headerRef;
    @ViewChild('content', { read: ViewContainerRef }) contentRef;

    ngOnInit() {
        this.loadSitecorePlaceholders(this.headerRef, 'generated-header');
        this.loadSitecorePlaceholders(this.contentRef, 'generated-content');
        // ...
    }
    private loadSitecorePlaceholders(containerRef: ViewContainerRef, placeholder: string) {
        // get the generated components listed from the sitecore placeholder
        const generatedContent = this.elementRef.nativeElement.parentNode.children[placeholder];
        if (generatedContent === undefined) { return; }
        if (containerRef === undefined) { return; }

        this.createComponentService.createComponentsFromHtml(containerRef, generatedContent.innerText);

        // we've finished creating all the components we need, remove the nodes created by sitecore.
        this.elementRef.nativeElement.parentNode.removeChild(generatedContent);
    }
}

In the CreateComponentService, we initialize an array of allowable component factories and a generic htmlHost component factory:

this._selectableFactories = creatableComponents
    .map((component: Type<any>) =>
        this.componentFactoryResolver.resolveComponentFactory(component));

We extract selector information from the CMS-generated script tag, then either add the component or inject it into the generic html component:

private createAngularComponent(
    container: ViewContainerRef,
    factory: ComponentFactory<Type<any>>,
    element: HTMLElement,
) {
    const selector = factory.selector.toLocaleLowerCase();
    container.createComponent(factory);
    // ...
}

private createHtmlComponent(container: ViewContainerRef, element: HTMLElement) {
    const component = container.createComponent(this.htmlHostFactory).instance;
    component.content = element.outerHTML;
}

Although the system is currently functioning well, performance remains a concern. With numerous components being loaded and compiled, there is a noticeable delay (3 to 4 seconds on a fast machine). To address this, we are taking two approaches:

  1. Transitioning to AOT instead of JIT. This progress has been hindered by an internal library issue, but we are actively resolving it.
  2. Implementing lazy loading of components upon request. Since only a subset of components is required on any given page, this optimization should yield performance gains.

For #2, all creatable components must be listed in entryComponents within the @NgModule. Is it necessary for all components to exist at generation? Additionally, while pre-loading factories allows us to find selectors, could this list be constructed during development?

Ideally, we envision using a dictionary of selectors linked to lazy component factories. Upon first call, the respective component would be downloaded and injected, enhancing perceived load speed post ngInit.

Is it feasible to lazily load components without router implementation, considering the dynamic composition? How can this be achieved?

Answer №1

There are several options available to you

1) To incorporate lazy loaded components into their own modules and load and compile them on demand, follow the instructions outlined in Here is what you need to know about dynamic components in Angular. You will need to utilize the

compileModuleAndAllComponentsAsync
method. Here is an example:

ngAfterViewInit() {
  System.import('app/t.module').then((module) => {
      _compiler.compileModuleAndAllComponentsAsync(module.TModule)
        .then((compiled) => {
          const m = compiled.ngModuleFactory.create(this._injector);
          const factory = compiled.componentFactories[0];
          const cmp = factory.create(this._injector, [], null, m);
        })
    })
}

2) Another option is to AOT compile components and load their factories on demand. The Angular AOT compiler produces AOT output as valid ES6/TS modules, allowing you to import the components individually and instantiate them directly from the factories:

  System.import('components/ex.component.ngfactory').then((factory) => {
       const cmp = factory.create(this._injector, [], null, m);
  })

Alternatively, you can obtain the module factory containing all the components and create an instance of it.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I encountered a TS error warning about a possible null value, despite already confirming that the value

In line 5 of the script, TypeScript raises an issue regarding the possibility of gameInstanceContext.gameInstance being null. Interestingly, this concern is not present in line 3. Given that I have verified its existence on line 1, it is perplexing as to w ...

Intercepting Nested Requests in a Nest.js Application

Can you explain how to incorporate a nested interceptor in Nest.js? I currently have two interceptors: UsersInterceptor and PostsInterceptor UsersInterceptor: @Injectable() export class UsersInterceptor<T> implements NestInterceptor<T, Response& ...

Make sure to review the JSON file prior to loading the essential modules

Utilizing Angular to construct my Project. Planning to modify the API endpoint post Project construction. To achieve this, I have generated a JSON file in the assets directory, with all service files retrieving data from this file. The content in the JSON ...

What's the deal with Angular query parameters and parentheses?

My Angular application has a routing structure that includes a query parameter. Here is an example: const routes: Routes = [{ path: '', component: DefaultComponent }, { path: ':uname', component: ProductDisplayComponent }]; Whe ...

I'm uncertain about the appropriate RabbitMQ subscription endpoint with STOMP

I'm using spring-boot-starter-amqp library to send messages to RabbitMQ. I've configured the exchange as spring-boot-exchange and set the routing key as group.n (where n is variable). I'm trying to retrieve these messages in Angular using n ...

The value of additionalUserInfo.isNewUser in Firebase is consistently false

In my application using Ionic 4 with Firebase/AngularFire2, I authenticate users with the signinwithemeailandpassword() method. I need to verify if it's the first time a user is logging in after registering. Firebase provides the option to check this ...

Angular2 has encountered a malfunction with the chart feature

My attempt to create a Bar Chart with this GitHub repository is not working in Chrome. You can view my code on Plunker. Can someone help me identify the issue? Below is the updated code: app.ts import {Component, Pipe, PipeTransform} from 'angular2 ...

Alter the style type of a Next.js element dynamically

I am currently working on dynamically changing the color of an element based on the result of a function //Sample function if ("123".includes("5")) { color = "boldOrange" } else { let color = "boldGreen" } Within my CSS, I have two clas ...

Using an External JavaScript Library in TypeScript and Angular 4: A Comprehensive Guide

My current project involves implementing Google Login and Jquery in Typescript. I have ensured that the necessary files are included in the project: jquery.min and the import of Google using <script defer src="https://apis.google.com/js/platform.js"> ...

The openapi-generator with the typescript-angular language option appears to be experiencing some issues

I am facing issues with generating angular code using the openapi-generator for language typescript-angular. Do you have any suggestions on how to resolve this? I have already tried running openapi-generator help meta and it seems that -l is a valid option ...

Typescript hack: Transforming read-only arrays into tuple types with string literals

My configuration object looks like this: const config = { envs: ['dev', 'test', 'prod'], targets: ['> 2%'] }; Currently, the TypeScript compiler interprets the type of this object as: type IConfig = { envs: str ...

What steps should I take to customize WebStorm so that it no longer automatically imports the entire Typescript paths?

Recently, I noticed a change in WebStorm after an update that affected how paths were imported in my files. Initially, when typing @Component and letting WebStorm automatically import the path, it would use the following format: import { Component } from ...

Angular is receiving HTML content instead of JSON from the response of the Django server

Here's the scenario: I'm running my Angular 8 code which involves making an HTTP GET request using ng serve while also running a Django Rest Service. return Response({"product":["mac","alienware"]}) (or) return JsonResponse({"product":["mac"," ...

Guide on how to switch a class on the body using React's onClick event

There's a button in my code that triggers the display of a modal-like div element. When this button is clicked, I aim to apply a class to the body element; then when the close button is clicked, I'll remove this class. I'm looking for guid ...

Issue: Formcontrolname attribute is undefined causing TypeError when trying to retrieve 'get' property.Remember to define formcontrolname attribute to

Having trouble creating a form at the moment and keep encountering this error: 'ERROR TypeError: Cannot read property 'get' of undefined' Even after trying various solutions like using formControlName in brackets or accessing the va ...

Is the latest Swiper JS version compatible with outdated web browsers?

Seeking information on browser compatibility. I am interested in upgrading to the latest version 8.4.5 of Swiper JS for my project, currently using version 4.1.6. Upon examining their shared Github repository file .browserslistrc, I noticed changes that ta ...

Importing/Requiring an External Module in Typescript Node using a Symbolic Link in the

I am in the process of migrating a Node + Express application to TypeScript and have encountered an issue with using external modules. Previously, I was utilizing the "symlink trick" to avoid dealing with relative paths. This is how it used to work withou ...

Typescript and MomentJS integration - What is the data type of the moment() function?

Currently in the process of upgrading my project from ES5 to ES6, I've encountered a problem with MomentJS (version 2.18.1). The issue arises when dealing with variables that are Moment objects and the inability to call moment() on them. For instance ...

The ngFor directive is not compatible with third-party library elements

I am currently working on an Angular application that utilizes a UMD library for the user interface. However, I have encountered a problem where I am unable to use the ngFor directive on an element from the library. Here is an example: <lib-select> & ...

Angular 10's tree shaking feature successfully eliminated the AsyncPipe module when setting sideEffects to false

Angular 10's tree shaking feature is causing unexpected issues with my AsyncPipe. A recent blog post on Angular 10's release notes introduces a new --strict mode for ng new: One interesting point it raises is: Setting up your app as side-effe ...