Dynamically load current components in Angular 2's final release

In my quest to dynamically load a component in the upcoming release version 2.0.0, I encountered some challenges.

Previously, in RC5, I utilized the following code for loading:

I created a directive responsible for loading the controls:

import {
  CheckboxComponent, CheckboxListComponent, DatePickerComponent
} from '../components/';

@Directive({
      selector: '[ctrl-factory]'
    })
    export class ControlFactoryDirective implements OnChanges {
      @Input() model: any;

      constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
      }

      create(cp) {
        this.resolver.resolveComponent(cp)
          .then(factory => {
            const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
            this.vcRef.createComponent(factory, 0, injector, []);
            let ch = this.vcRef.createComponent(factory, 0, injector, []).instance;
            ch.model = this.model;
        });
      }

      ngOnChanges() {
        if (!this.model) return;

        switch (this.model.type) {
          case 'checkbox':
            this.create(CheckboxComponent);
            break;
          case 'checkboxlist':
            this.create(CheckboxListComponent);
            break;
          case 'datepicker':
            this.create(DatePickerComponent);
            break;
          default:
            break;
        }
      }
    }

Then, I included that directive on my page like so:

<div ctrl-factory *ngFor="let child of page.childrens" [model]="child"></div>

However, upon transitioning from rc5 to the final release version 2.0.0, I realized that the resolver was replaced by compiler.

I attempted to follow various suggestions on how to achieve this with different codes, but they all seemed too complex and I couldn't get it to work.

For example, check out this link: How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

Although it may be more tailored to a specific scenario, I simply needed to load the component and set an @Input named model.

During my attempts, I had to dynamically create a module for each component and add the component to it. However, I encountered issues indicating that the component was being added to more than one Module, despite trying to remove it unsuccessfully.

The majority of the code I used was sourced from this link:

I made a few modifications based on that reference.

Update

I eventually managed to make it functional by adopting the following approach:

The create method underwent a transformation as follows:

private create(cp) {
    @NgModule({
      imports: [BrowserModule, ControlsModule],
      declarations: []
    })
    class DynamicModule {}

    this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
      .then(({componentFactories}) => {
        const compFactory = componentFactories.find(x => x.componentType === cp);
        const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
        const cmpRef = this.vcRef.createComponent(compFactory, 0, injector, []);
        cmpRef.instance.model = this.model;
      });
  }

Many sources suggested creating the Component and assigning it to the DynamicModule. However, this led to conflicts when the same component was declared in a different module. In my case, importing my ControlsModule - which contains all exported controls - proved to be the solution.

Answer №1

Latest News Update!

New Release: The NgComponentOutlet feature was first introduced in version 4.0.0-beta.3. Check it out on GitHub here.

Exciting update coming soon! Click here to learn more about NgComponentOutlet.

Interested in implementing this new feature? Here are two options for you:

1) Method using ComponentFactoryResolver:

This method involves utilizing the pre-generated factory and code structure as shown below:

constructor(private vcRef: ViewContainerRef, private resolver: ComponentFactoryResolver) { }
create(comp) {
  const factory = this.resolver.resolveComponentFactory(comp);
  const compRef = this.vcRef.createComponent(factory);

  (<any>compRef).instance.model = this.model;
}

In this case, make sure to define dynamic components in the declarations and entryComponents properties within the module's decorator.

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, DynamicComponent ],
  entryComponents: [DynamicComponent],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

2) Approach using Compiler:

In this scenario, you can execute module compilation by invoking

compiler.compileModuleAndAllComponentsAsync
and then locating the component from the componentFactories array. Below is a sample directive implementation:

constructor(private vcRef: ViewContainerRef, private loader: DynamicLoaderService) { }

create(comp) {
  this.loader.createComponentFactory(comp).then(factory => {
     const compRef = this.vcRef.createComponent(factory);

    (<any>compRef).instance.model = this.model;
  })
}

The DynamicLoaderService serves as a global service responsible for loading and storing component factories securely.

@Injectable()
export class DynamicLoaderService {
  constructor(protected compiler: Compiler) {}

  // Additional methods and caching logic for managing component factories

}

Explore a live example via Plunker.

Feel free to ask any questions or seek further assistance. Hope this information proves helpful!

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

Unable to open fancybox from a skel-layer menu

Seeking assistance with integrating a Fancybox inline content call from a Skel-layer menu (using the theme found at ) <nav id="nav"> <ul> <li><a href="#about1" class="fancybox fancybox.inline button small fit" >about< ...

The application is having trouble accessing the property 'isXXXXX' because it is undefined

My attempt to utilize a shared service in one of my components has been successful when used with the app's root component. However, I encountered an error when trying to implement it on another module or dashboard. shared/authorize.service.ts @Inje ...

Ivy workspace encountered an error with Angular being undefined: <error>

Recently, I attempted to install Angular on my MacOS terminal by entering the command $ npm install -g @angular/cli Unfortunately, the installation kept failing and the terminal displayed an error message. Subsequently, I tried the command $ sudo npm inst ...

Concealing objects identified by a specific id

I have two separate div elements var promptContent = $('<div id="errorr">').addClass("formErrorContent").html(promptText).appendTo(prompt); var arrow = $('<div id="errorrarrow">').addClass("formErrorArrow"); I need to refe ...

Encountering an issue: Module """ not located at webpackMissingModule

I'm facing an issue while trying to webpack my express application. Specifically, I encounter the following problem whenever I attempt to access the / page: Encountering Error: Cannot find module "." at webpackMissingModule Below is a snippet of c ...

Tips on enlarging the header size in ion-action-sheet within the VueJS framework of Ionic

Recently I started using Vue along with the ionic framework. This is a snippet of code from my application: <ion-action-sheet :is-open="isActionSheetOpen" header="Choose Payment" mode="ios" :buttons="buttons&qu ...

The AWS Lambda function in Node.js encountered an internal server error

I've been experimenting with AWS Lambda functions, Axios, and Cheerio in a demo project. However, when I call the endpoint, I receive an error message saying {message: Internal Server Error} exports.lambdaHandler = async (event, context) => { ...

Encountering difficulties in populating mongoose document following a model query

Hi everyone, this is my first time posting on SO so I'm hoping for some positive outcomes. I've been using Mongoose in a current project without any issues until now. The problem arises when trying to populate object references after querying fo ...

Error: Invalid Argument - The argument 'PanelController' is expected to be a function, but it is undefined

Here is a snippet of my HTML code: <body ng-controller="StoreController as store"> ........... <section ng-controller="PanelController as panel"> <ul class="nav nav-pills""> <li ng-class="{active:panel.isSe ...

What is the best way to access the input element of ng-content from the parent component?

Creating a unique component in the following structure <div class="custom-search-field" [ngClass]="{'expanding': expanding}"> <ng-content></ng-content> </div> When using this component, users are expected to include ...

Different ways to showcase a value from the CSS file on the console using console.log

In this guide, you can learn how to create a custom directive in Angular by following this tutorial: Custom Directive Tutorial. The directive should function as intended. Still, I want to see the color value set in the CSS file displayed on the console us ...

Shifting Icon to the Right within the Drawer Navigator Toolbar

While modifying the example code for Material UI's drawer navigator, I decided to enhance it by adding a notification icon and a checkout icon with the Admin Panel typography in the toolbar. However, I encountered an issue where the checkout icon app ...

Utilizing JavaScript regex for patterns such as [x|y|z|xy|yz]

Looking for a regex solution: \[[^\[\]]*\]\s*[<>|<=|>=|=|>|<]\s*'?"?\w*'?"? This regex is designed to parse equations like: [household_roster_relationships_topersona_nameadditionalpersono] = ...

Navigating between components using AngularJS and ExpressJS

Currently, I am embarking on a project that involves utilizing express and angularjs. To guide me through this process, I have been referring to this particular tutorial. Upon my initial attempt of running localhost:3000, I successfully loaded my index.jad ...

JavaScript/AJAX Functionality Only Operates Correctly During Debugging

I am currently facing an issue with dynamically populating a website. The code works perfectly when I step through it, but it fails to work as intended on page load. Here is the relevant code snippet: <body onload="populate_all(string)";> function ...

I must add and display a tab for permissions

I am currently using the material UI tab for my project. My goal is to display the tab only when the permission is set to true. I have successfully achieved this functionality, but the issue arises when the permission is false. It results in an error that ...

What is the way to retrieve an array property in a typescript interface?

Imagine a scenario with three interfaces structured as follows: registration-pivot.ts export interface RegistrationPivot { THead: RegistrationPivotRow; TBody: RegistrationPivotRow[]; } registration-pivot-row.ts export interface RegistrationPivotR ...

Modify the name of the document

I have a piece of code that retrieves a file from the clipboard and I need to change its name if it is named image.png. Below is the code snippet where I attempt to achieve this: @HostListener('paste', ['$event']) onPaste(e: ClipboardE ...

The specified starting point "@angular/material/card" cannot be accessed due to missing dependencies

When attempting to deploy a web application on Heroku, I encountered the following error message: - Generating browser application bundles (phase: setup)... Compiling @angular/core : es2015 as esm2015 Compiling @angular/common : es2015 as esm2015 Compiling ...

Steps for deploying Nuxt on a subdirectory using either Express JS or Plesk

I have configured my Nuxt (Node.js application) to run on Plesk. This means that Plesk will execute the server.js file, which in turn runs ExpressJS and then Nuxt. Here is the content of my server.js file: const express = require('express') cons ...