Develop a dynamic component using ComponentFactoryResolver that implements a dynamic template

I am looking to create a unique dynamic component with a custom template that resolves interpolations based on the dynamic component's context.

To achieve this, I understand that I can utilize the following code snippet to generate a dynamic component (ensure it is listed in entryComponents within the module):

Here is my initial static component:

@Component({
  selector: 'html-type',
  template: `<ng-template #htmlcontrolcomponent></ng-template>`
})
export class HtmlTypeComponent implements AfterViewInit{

  @ViewChild('htmlcontrolcomponent', { read: ViewContainerRef }) entry: ViewContainerRef;
  constructor(private resolver: ComponentFactoryResolver) {
    super();
   }

  ngAfterViewInit() {
    this.createComponent("<div>{{contextvar}}</div>");
  }

  createComponent(template) {
    this.entry.clear();
    const factory = this.resolver.resolveComponentFactory(HtmlControlComponent);
    const componentRef = this.entry.createComponent(factory);
    componentRef.instance.template = template;       // seeking alternative methods 
  }

This is the component to be dynamically included:

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

@Component({
  selector: 'html-control',
  template: '',
})
export class HtmlControlComponent {
   contextvar: string = "This is my current context";
}

Is there a way to update the template of a dynamically generated component?

The goal here is for the dynamic component's template to be customizable, allowing user input and sanitization. Can you suggest a method?

Answer №1

Successfully accomplished it using a unique method

Utilized the DynamicComponentService for the task

Crucial Note: Disabled "aot: false" in angular.json to avoid encountering Runtime compiler is not loaded errors.

import {
  Compiler,
  Component,
  ComponentFactory,
  Injectable,
  NgModule,
  Type,
  ViewContainerRef,
  ViewEncapsulation
} from "@angular/core";
import {CommonModule} from "@angular/common";

@Injectable({
  providedIn: "root"
})
export class DynamicComponentService {

  protected factoryCache: {[key: string]: ComponentFactory<any>};
  protected componentCache: {[key: string]: Type<any>};
  protected moduleCache: {[key: string]: Type<any>};

  constructor(protected compiler: Compiler) {
    this.factoryCache = {};
    this.componentCache = {};
    this.moduleCache = {};
  }

  /**
   *
   * @param viewContainerRef
   * @param selector
   * @param template
   */
  createDynamicComponent(viewContainerRef: ViewContainerRef, selector: string, template: string) {
    const foundComponent = this.componentCache[selector];
    if(foundComponent) {
      this.compiler.clearCacheFor(foundComponent);
      delete this.componentCache[selector];
    }
    const foundModule = this.moduleCache[selector];
    if(foundModule) {
      this.compiler.clearCacheFor(foundModule);
      delete this.moduleCache[selector];
    }

    viewContainerRef.clear();

    this.componentCache[selector] = Component({
      selector,
      template,
      encapsulation: ViewEncapsulation.None
    })(class {
    });

    this.moduleCache[selector] = NgModule({
      imports: [CommonModule],
      declarations: [this.componentCache[selector]]
    })(class {
    });

    return this.compiler.compileModuleAndAllComponentsAsync(this.moduleCache[selector])
      .then((factories) => {
        const locatedFactory = factories.componentFactories.find((factory) => {
          return factory.selector === selector;
        });

        if(locatedFactory) {
          return viewContainerRef.createComponent(locatedFactory);
        }

        throw new Error("component not found");
      })
      .catch((error) => {
        console.log("error", error);

        this.compiler.clearCacheFor(foundComponent);
        delete this.componentCache[selector];
        this.compiler.clearCacheFor(foundModule);
        delete this.moduleCache[selector];

        return Promise.reject(error);
      });
  }

}

Furthermore, updated my HTML-type component as follows:

export class CustomHtmlComponent implements DoCheck {

  @ViewChild('htmlcontrolcomponent', { read: ViewContainerRef }) entry: ViewContainerRef;

  protected previousTemplate: string = "";
  protected componentReference?: ComponentRef<any>;

  constructor(private dynamicComponentService: DynamicComponentService) {}

   ngDoCheck() {
    if(this.entry && this.previousTemplate !== this.to.template) {
      this.previousTemplate = this.to.template;

      if(this.componentReference) {
        this.componentReference.destroy();
        delete this.componentReference;
      }

      this.entry.clear();

      this.dynamicComponentService.createDynamicComponent(this.entry, 'html-content', this.to.template)
        .then((component) => {
          this.componentReference = component;
          this.componentReference.instance.model = this.model;
        });
    }
  }

}

It was also possible to eliminate the need for the HtmlControlComponent

Answer №2

One potential solution is to leverage the <ng-content> element along with the projectableNodes parameter of the createComponent() function. Consider implementing the following:

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

@Component({
  selector: 'html-control',
  template: `<ng-content></ng-content>`,      // <-- include `<ng-content>` here
})
export class HtmlControlComponent {
   contextvar: string = "This is my current context";
}

This defines a static component.

createComponent(template) {
  this.entry.clear();
  const factory = this.resolver.resolveComponentFactory(HtmlControlComponent);
  const componentRef = this.entry.createComponent(factory, 0, undefined, [[template]]);    // <-- include `template` here
}

The argument [[template]] is passed to the projectableNodes parameter as an array of arrays (any[][])


Please note that this code has not been tested and may require some adjustments.

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

Changing the key of a JavaScript request object into a string variable

Just starting out with programming. The API post call requires an object variable (derived from a variable) to be passed as a string like this: "option": { "235": “30” }, { "238": “32” } In my Angular 6 code: ...

Typescript fails to recognize a value assigned within an await statement

Looking at the code snippet below, we see that the variable x starts off undefined and can later be assigned a value of 1 within an `await` promise. Despite setting x to 1 inside the awaited promise, TypeScript still perceives it as undefined after the pr ...

Creating custom web components with routing in Angular 6

Is it possible to create an Angular WebComponent or Custom Element with routing modules in Angular 6? I have successfully created a web component with atomic components and now I want to expand it to have multiple screens using the routing module. Here ar ...

Angular 6: Issue with displaying data on the user interface

Hello! I am attempting to fetch and display a single data entry by ID from an API. Here is the current setup: API GET Method: app.get('/movies/:id', (req, res) => { const id = req.params.id; request('https://api.themoviedb.org/ ...

Challenges encountered while deploying a NextJS project with TypeScript on Vercel

Encountering an error on Vercel during the build deploy process. The error message says: https://i.stack.imgur.com/Wk0Rw.png Oddly, the command pnpm run build works smoothly on my PC. Both it and the linting work fine. Upon inspecting the code, I noticed ...

Can Angular Routing support distinct 'path' strings for various languages?

I've been working on an internationalized Angular app, and it's performing really well so far. However, I've noticed that I have the same route strings for different languages, which could negatively impact SEO. Is there a way to make the ...

Implementing form validation for mandatory fields upon submission in Angular 17

There are several fields such as firstName and lastName that are marked as required on the backend. If the form is submitted without entering the firstName, an error is displayed in the Network Preview. Similarly, if the firstName is filled but the lastNam ...

how can I display the JSON description based on the corresponding ID using Ionic 3

I have a JSON file containing: [{ "id": "1", "title": "Abba Father (1)", "description": "Abba Abba Father." }, { "id": "2", "title": "Abba Father, Let me be (2)", "description": "Abba Father, Let me be (2) we are the clay." }, { ...

Modify the data type of an object member based on its original type

I am seeking to convert the data type of each member in an object based on the specific member variable. Here is an example: class A { fct = (): string => 'blabla'; } class B { fct = (): number => 1; } class C { fct = (): { o ...

showing javascript strings on separate lines

I need assistance with displaying an array value in a frontend Angular application. How can I insert spaces between strings and show them on two separate lines? x: any = [] x[{info: "test" + ',' + "tested"}] // Instead of showing test , teste ...

Retrieve a specific subdirectory from the bundle

I've developed a Typescript package with the following file structure: package.json src/ index.ts common/ index.ts sub/ index.ts My goal is to import certain modules from the package like this: import {...} from '<package>&ap ...

"Obtaining subnet identification using the name or CIDR: A step-by-step

I am seeking help on retrieving the subnet id based on subnet name or cidr in order to deploy a nat gateway. Can someone provide guidance on how to obtain the subnet id? Alternatively, does anyone have any best practices for utilizing typescript function ...

Having trouble getting the styles property to work in the component metadata in Angular 2?

Exploring Angular 2 and working on a demo app where I'm trying to apply the styles property within the component metadata to customize all labels in contact.component.html. I attempted to implement styles: ['label { font-weight: bold;color:red } ...

Encountering a build error in ng serve right after running npm install

After deleting the node_modules directory and rebuilding it with npm install, I encountered an error in my angular2 app when using cmd ng serve. Error: 'common-tags' module not found at Function.Module._resolveFilename (module.js:337:15) ...

How can one store the value of a promise or observable in an external variable?

I have thoroughly researched the .then() method and comprehend its functionality. In my current project, it is successfully providing me with the desired value. async getDay() { try { let ref = this.db.getDay(this.dateFirebase); ref.then(o ...

Can a default value be assigned to a generic argument in Typescript?

I'm currently creating versatile methods for use across various frontend applications. The goal is to invoke the function .postAsync<CustomModel>('www.mysite.com',..., CustomModel); and receive a CustomModel object as the response. I ...

Having trouble personalizing the background color according to the ItemName in angular2-multiselect-dropdown

Is it possible to customize the background color in angular2-multiselect-dropdown based on the tags label? I want the background color to be determined by the tags label. The npm package provides no description regarding this feature here https://i.ssta ...

Encountering build issues in my next.js application post updating to version 12.#.# and implementing Typescript

In my next.js application, I recently upgraded to version 10 and added TypeScript to the mix. Despite ironing out all issues during development, I encountered errors when running yarn next build due to my use of the keyword interface. ./src/components/ ...

The functionality of connect-flash in Express JS is compromised when used in conjunction with express-mysql-session

I am facing a unique issue in my project. I have identified the source of the problem but I am struggling to find a solution. My project utilizes various modules such as cookie-parser, express-mysql-session, express-session, connect-flash, passport and m ...

Moving from the landing page to a specific tab in Ionic 2+ using dynamic navigation

Upon launching the application, users are greeted with a landing page called IntroPage, featuring two prominent buttons labeled BUY and SELL. Clicking on either of these buttons will navigate users to the corresponding buy or sell Tab. In my quest to achi ...