What is the best way to programmatically insert an Angular2 sub component using TypeScript code?

Context - I am currently working on developing a custom dropdown feature that can house various components. While I initially thought about using the <ng-content> tag for this purpose, my team prefers a solution where the dropdown setup is done mainly through TypeScript code.

I considered using DynamicComponentLoader to achieve this, but most of the tutorials I came across referenced the loadIntoLocation() function, which is no longer available. Instead, I attempted to utilize the loadAsRoot() function, but encountered some challenges in making it work.

Here is my approach:

Main.ts:

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

@Component({
    selector: 'my-app',
    template: `
        <my-dropdown [contentModels]="dropdownContentModels"></my-dropdown>
    `
})
export class Main {
    dropdownContentModels: any[];
    constructor() {
        var someComponentModel = {selector: 'some-component', text: 'some'};
        var otherComponentModel = {selector: 'other-component', text: 'other'};
        this.dropdownContentModels = [someComponentModel, otherComponentModel];
    }
}

MyDropdown.ts:

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

@Component({
    selector: 'my-dropdown',
    inputs: ['contentModels'],
    directives: [InjectComponent],
    template: `
        <div class="btn-group" dropdown>
            <button type="button" dropdownToggle>My Dropdown</button>
            <div class="dropdown-menu" role="menu">
                <inject-component *ngFor="let item of contentModels" [model]="item"></inject-component>
            </div>
        </div>
    `
})
export class MyDropdown {
    contentModels: any[];
}

InjectComponent.ts:

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

@Component({
    selector: 'inject-component',
    inputs: ['model'],
    template: `
        <div #toreplace></div>
    `,
    providers: [DynamicComponentLoader, Injector]
})
export class InjectComponent {
    model: any;
    constructor(private dcl: DynamicComponentLoader, private injector: Injector) {}
    ngOnInit() {
        this.dcl.loadAsRoot(this.createWrapper(), '#toreplace', this.injector);
    }
    createWrapper(): any {
        var model = this.model;
        @Component({
            selector: model.selector + '-wrapper',
            template: '<' + model.selector + ' [model]="model"></' + model.selector + '>'
        })
        class Wrapper {
            model: any = model;
        }

        return Wrapper;
    }
}

However, I am encountering a runtime exception stating "EXCEPTION: Error: Uncaught (in promise): Can only add to a TokenMap! Token: Injector"

Update! (Thanks to echonax):

InjectComponent.ts:

import { Component, ComponentResolver, ViewChild, ViewContainerRef, 
    ComponentFactory, ComponentRef } from '@angular/core';

@Component({
    selector: 'inject-component',
    inputs: ['model'],
    template: `
        <div #toreplace></div>
    `
})
export class InjectComponent {
    model: any;
    @ViewChild('toreplace', {read: ViewContainerRef}) toreplace;
    componentRef: ComponentRef<any>;

    constructor(private resolver: ComponentResolver) {}

    ngOnInit() {
        this.resolver.resolveComponent(this.createWrapper()).then((factory:ComponentFactory<any>) => {
            this.componentRef = this.toreplace.createComponent(factory);
        });
    }
    createWrapper(): any {
        var model = this.model;
        @Component({
            selector: model.selector + '-wrapper',
            directives: [ model.directives ],
            template: '<' + model.selector + ' [model]="model"></' + model.selector + '>'
        })
        class Wrapper {
            model: any = model;
        }

        return Wrapper;
    }
}

Answer №1

To incorporate a new component, you can utilize the .createComponent() function.

import {ComponentRef, Injectable, Component, Injector, ViewContainerRef, ViewChild,ComponentResolver, DynamicComponentLoader} from '@angular/core';

export class InjectComponent {
    @ViewChild('toreplace', {read: ViewContainerRef}) toreplace;   


    constructor(private dcl: DynamicComponentLoader, injector: Injector,private resolver: ComponentResolver) {}

...

    this.resolver.resolveComponent((this.createWrapper()).then((factory:ComponentFactory<any>) => {
          this.cmpRef = this.theBody.createComponent(factory)
        });

Furthermore, it is suggested to eliminate

providers: [DynamicComponentLoader, Injector]

Refer to this sample plunker which exemplifies the use of DynamicComponentLoader (in app.component.ts): https://plnkr.co/edit/azoGdAUvDvCwJ3RsPXD6?p=preview

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

Comparing the mechanics of data binding between Angular 2 and Angular 1.x

Back in the days of Angular 1.x, we relied on the digest cycle to trigger watchers and update the view whenever a binded property changed. However, with Angular 2, we now have one-way binding through interpolation in the view. But how exactly does this one ...

Managing clicks outside of the render function

I'm brand new to using React and I'm currently exploring how to properly manage an event handler outside of the Return() function within a component. If there's a more efficient or conventional way to do this, I'm definitely open to sug ...

Display a popup in Angular 4 when a click event is triggered by a separate

I am currently facing an issue that I can't seem to solve. My objective is to display a popup div on the page when clicking on a menu entry in my navbar.component. In order to achieve this, I introduced a property called "show" in my popup component ...

Leveraging an external Typescript function within Angular's HTML markup

I have a TypeScript utility class called myUtils.ts in the following format: export class MyUtils { static doSomething(input: string) { // perform some action } } To utilize this method in my component's HTML, I have imported the class into m ...

Rendering React component within a production build of Angular 7

I've been in the process of gradually moving an Angular app to React. After exploring options like single-spa and nx, I found that they weren't suitable due to the messy script-injected nature of the existing app. So, I decided to go for a semi-m ...

Assigning a Value to a Dropdown Menu in Angular

I have some JSON data that contains a True/False value. Depending on whether it is true or false, I want a specific option in a Select Dropdown to be automatically selected. This is the HTML code using Angular 16: <select name="reportNo" id=& ...

Approach to Monitoring Notifications

Is there a best practice for managing notifications in an AngularJS application? When I mention 'notifications', I am referring to alerts that should be displayed to the user while they are logged into the app. My idea is to show the user any u ...

table displaying data with multiple footers

I am trying to design a table with additional footer rows that are not directly linked to the dataSource of the mat-table, but rather to another component-specific dataSource. I should clarify that I do not need multiple footers (as I haven't found a ...

Distinguish between multiple occurrences of the same component in Angular 2

I've recently started learning Angular 2 and have a query regarding components. I have created a component called "dropdownComponent" that generates dropdown components. However, when using this component multiple times, I'm unsure how to differe ...

Issue with Protractor .sendKeys() method when interacting with input fields bound with ngModel

I have searched extensively for information on this topic, but with no luck. That's why I've decided to reach out and ask. Currently, I am in the process of writing E2E tests for my Angular 5 application. The testing is being done using Protract ...

Hand over the component method as an argument to a class

One of my components, called First, is responsible for creating a new instance of a Worker class. During the creation process of this class, I intend to pass the Read method as a callback method. Once this class completes its task, it will then invoke thi ...

Utilizing the Pub/Sub architecture to integrate the kafka-node library within Node Js

Utilizing the kafka-node module in my NodeJs Microservise project, I am aiming to implement a Pub/Sub (publisher and subscriber) design pattern within the Functional programming paradigm. producer.js const client = new kafka.KafkaClient({ kafkaHost: ...

Conditioning types for uninitialized objects

Is there a way to create a conditional type that can determine if an object is empty? For instance: function test<T>(a: T): T extends {} ? string : never { return null } let o1: {} let o2: { fox? } let o3: { fox } test(o1) ...

I'm on the lookout for a component similar to angular-ui-tree that is compatible with angular

As a new developer, I am in search of a component similar to: But specifically for Angular 6, with all the same functionality (drag-and-drop capability, nested items, JSON structure, etc.). I have come across some components that either lack dragging fun ...

Utilizing an asynchronous function in ES6 Vue.js and TypeScript

I'm currently working on optimizing my code for reusability, and I've decided to store multiple async functions in a separate file. blog.ts import db from '@/firebase/init' async function fetchTags (uid) { const tags = db.collecti ...

What is the method for using the pipe to convert currency rates to a specific currency type?

I am working on a project where I need to display currency rates in the selected currency type all over the page. I have a dropdown with various currency types and want to dynamically update the rates based on the selected currency. After some research, I ...

Deactivate multiple input fields by utilizing various radio buttons

I need help with enabling and disabling input fields based on radio button selection. When the first radio button is selected, I want to disable three input fields, when the second is selected, only two specific input fields should be enabled (SHIFT START ...

Automatic type inference for TypeScript getters

It appears that Typescript infers field types based solely on the private variable of a field, rather than taking into account the getter's return type union (1) or inferring from the getter itself (2): test('field type inference', () =& ...

The CORS policy is refusing access to XMLHttpRequest from 'http://localhost:4200' to 'http://localhost:8082/kanchiwork/'

While attempting to transfer values from one server to another, I encountered an error stating "Access to XMLHttpRequest at 'http://localhost:8082/kanchiwork/' from origin 'http://localhost:4200' has been blocked by CORS policy". My .ts ...

Bidirectional data binding in Angular with unique quirks in Internet Explorer version 9

As I develop my Angular application, I have implemented the Angular two-way binding feature for all input fields. It is crucial that this application is compatible with IE versions 9 and above. While testing on the latest IE versions such as 10 and Edge, t ...