Implementing Angular - Injecting a component dynamically into another component

Currently, I am working on developing a small UI components framework for my personal use and enjoyment. One of the components I'm working on is a Tab component. To test this component, I need to dynamically inject another component (TabContainerComponent) into it. Here is the code for both of my components:

tab.component.ts:

import {Component, ContentChildren} from "@angular/core";
import {TabContainerComponent} from "./tabContainer.component";

@Component({
    selector: 'tab',
    templateUrl: 'tab.component.html'
})
export class TabComponent {

    @ContentChildren(TabContainerComponent)
    tabs: TabContainerComponent[];
}

tab.component.html:

<ul>
    <li *ngFor="let tab of tabs">{{ tab.title }}</li>
</ul>
<div>
    <div *ngFor="let tab of tabs">
        <ng-container *ngTemplateOutlet="tab.template"></ng-container>
    </div>
    <ng-content></ng-content>
</div>

tabContainer.component.ts:

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

@Component({
    selector: 'tab-container',
    template: '<ng-container></ng-container>'
})
export class TabContainerComponent {

    @Input()
    title: string;

    @Input()
    template;
}

To dynamically create and inject the new TabContainerComponent, I utilized ComponentFactoryResolver and ComponentFactory in the addTab method of my other component (TabContainer):

app.component.ts:

import {
    Component, ViewChild, ComponentFactoryResolver, ComponentFactory,
    ComponentRef, TemplateRef, ViewContainerRef
} from '@angular/core';
import {TabContainerComponent} from "./tabContainer.component";
import {TabComponent} from "./tab.component";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

    title = 'app';

    @ViewChild(TabComponent)
    tab: TabComponent;

    @ViewChild('tabsPlaceholder', {read: ViewContainerRef})
    public tabsPlaceholder: ViewContainerRef;

    @ViewChild('newTab')
    newTab: TemplateRef<any>;

    constructor(private resolver: ComponentFactoryResolver) {
    }

    addTab(): void {
        let factory: ComponentFactory<TabContainerComponent> = this.resolver.resolveComponentFactory(TabContainerComponent);
        let tab: ComponentRef<TabContainerComponent> = this.tabsPlaceholder.createComponent(factory);
        tab.instance.title = "New tab";
        tab.instance.template = this.newTab;
        console.log('addTab() triggered');
    }
}

The addMethod is triggered by clicking on the "Add tab" button:

app.component.html:

<button (click)="addTab()">Add tab</button>
<tab>
    <tab-container title="Tab 1" [template]="tab1"></tab-container>
    <tab-container title="Tab 2" [template]="tab2"></tab-container>
    <ng-container #tabsPlaceholder></ng-container>
</tab>
<ng-template #tab1>T1 template</ng-template>
<ng-template #tab2>T2 template</ng-template>
<ng-template #newTab>
    This is a new tab
</ng-template>

app.module.ts:

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


import { AppComponent } from './app.component';
import {TabContainerComponent} from "./tabContainer.component";
import {TabComponent} from "./tab.component";


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

Although the dynamic injection works, Angular does not update the view of the Tab component after adding a new tab. I have tried implementing the OnChanges interface in TabComponent without success.

If you have any ideas on how to solve this issue, please let me know!

P.S.: I prefer not using an array of TabContainer components to test the createComponent method.

Update:

Demo:

https://stackblitz.com/edit/angular-imeh71?embed=1&file=src/app/app.component.ts

Answer №1

My solution to make it work is as follows -

Tab.component.ts

I updated the "tabs" property from an array of TabContainerComponent to a QueryList.

import { Component, ContentChildren, QueryList } from '@angular/core';
import { TabContainerComponent } from '../tab-container/tab-container.component';

@Component({
  selector: 'app-tab',
  templateUrl: 'tab.component.html'
})
export class TabComponent {
  @ContentChildren(TabContainerComponent)
  tabs: QueryList<TabContainerComponent>;

  constructor() {}
}

Next, I added a new template in app.component.html

<ng-template #tabContainerTemplate>
  <app-tab-container title="New Tab" [template]="newTab"></app-tab-container>
</ng-template>

app.component.ts

import {
  Component,
  ViewChild,
  TemplateRef,
  ViewContainerRef,
  AfterViewInit,
  ViewChildren,
  QueryList,
  ChangeDetectorRef
} from '@angular/core';
import { TabContainerComponent } from './tab-container/tab-container.component';
import { TabComponent } from './tab/tab.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  title = 'app';
  changeId: string;
  @ViewChild(TabComponent) tab: TabComponent;
  @ViewChild('tabsPlaceholder', { read: ViewContainerRef })
  public tabsPlaceholder: ViewContainerRef;
  @ViewChild('tabContainerTemplate', { read: TemplateRef })
  tabContainerTemplate: TemplateRef<null>;
  @ViewChildren(TabContainerComponent)
  tabList: QueryList<TabContainerComponent>;

  constructor(private changeDetector: ChangeDetectorRef) {}

  ngAfterViewInit() {}

  addTab(): void {
    this.tabsPlaceholder.createEmbeddedView(this.tabContainerTemplate);
    this.tab.tabs = this.tabList;
    this.changeDetector.detectChanges();
    console.log('addTab() triggered');
  }
}

I included a ViewChildren query for TabContainerComponent and used createEmbeddedView in addTab() to add a new tab container component.

Although I expected the "ContentChildren" query in TabComponent to update with the newly added component, it did not. Despite attempting to subscribe to "changes" for the query list in TabComponent, it remained untriggered.

However, I noticed that the "ViewChildren" query in AppComponent was consistently updated whenever a new component was added. So, I reassigned the updated QueryList of the app component to the QueryList of TabComponent.

You can view the working demo here

Answer №2

Your code has been updated successfully and now displays as expected on the view.

Check out the Stackblitz demo here

If you want to create dynamic components, you'll need to use TemplateRef to generate Embedded Views.

The View Container provides an API for creating, manipulating, and removing dynamic views.

For more information on Dynamic Component Manipulation, visit: this link

  
import {
    Component, ViewChild, ComponentFactoryResolver, ComponentFactory,
    ComponentRef, TemplateRef, ViewContainerRef, AfterViewInit
} from '@angular/core';
import {TabContainerComponent} from "./hello.component";
import {TapComponent} from "./tap/tap.component";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {

    title = 'app';
   @ViewChild('vc',{read:ViewContainerRef}) vc:ViewContainerRef;
    @ViewChild(TapComponent)
    tab: TapComponent;

    @ViewChild('tabsPlaceholder', {read: ViewContainerRef})
    public tabsPlaceholder: ViewContainerRef;

    @ViewChild('newTab')
    newTab: TemplateRef<any>;

    constructor(private resolver: ComponentFactoryResolver) {
    }

  ngAfterViewInit(){


  }

    addTab(): void {
        let factory: ComponentFactory<TabContainerComponent> = this.resolver.resolveComponentFactory(TabContainerComponent);
        let tab: ComponentRef<TabContainerComponent> = this.tabsPlaceholder.createComponent(factory);
        tab.instance.title = "New tab";
        tab.instance.template = this.newTab;
        this.vc.createEmbeddedView(this.newTab);
        console.log('addTab() triggered');
    }
}

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

Leverage the power of Angular CLI within your current project

I am currently working on a project and I have decided to utilize the angular cli generator. After installing it, I created the following .angular-cli file: { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "project": { "name": " ...

A step-by-step guide on effectively adopting the strategy design pattern

Seeking guidance on the implementation of the strategy design pattern to ensure correctness. Consider a scenario where the class FormBuilder employs strategies from the following list in order to construct the form: SimpleFormStrategy ExtendedFormStrate ...

Obtain a union type in TypeScript based on the values of a field within another union type

Is it feasible in Typescript to derive a union type from the values of a field within another union type? type MyUnionType = | { foo: 'a', bar: 1 } | { foo: 'b', bar: 2 } | { foo: 'c', bar: 3 } // Is there an automati ...

What is the reason for the return of undefined with getElementsByClassName() in puppeteer?

Currently, I am utilizing puppeteer to fetch certain elements from a webpage, specifically class items (divs). Although I understand that getElementsByClassName returns a list that needs to be looped through, the function always returns undefined for me, e ...

Using AngularJS with CDN: A beginner's guide

If I need to create an app using AngularJS with Cordova in Visual Studio, do I need anything else besides the Google CDN for AngularJS? <!doctype html> <html ng-app> <head> <title>My Angular App</title> <script s ...

A Guide to Filtering MongoDB Data Using Array Values

I am trying to extract specific data from a document in my collection that contains values stored in an array. { "name": "ABC", "details": [ {"color": "red", "price": 20000}, {" ...

Guide to setting up an interface-only project (along with its dependent projects) on NPM

I'm encountering two problems with my TypeScript project. Imagine I have a interface-only TypeScript project called myproject-api. My plan is to implement the interfaces in two separate projects named myproject-impl1 and myroject-impl2. I am utilizin ...

How do decorators within Angular 2 impact components?

Can you help me understand the purpose of decorators in angular2? How do they allow for annotating and modifying classes and properties during design time? It seems similar to using annotations, could you clarify this concept for me please? Thank you in ...

Combining files/namespaces/modules in Typescript: How to do it?

Even though I believe the solution may be simple, understanding how to merge enums across multiple files is eluding me when reading through the documentation. // a.ts enum Color{ RED, BLUE } // b.ts enum Day{ MONDAY, TUESDAY } // c ...

Tips on how to verify if the Angular test with native elements has produced an error

Is there a way to detect errors in an Angular test that uses native elements? The test I am running is as follows: import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MikeComponent } from './mike.component&a ...

Angular animation triggered when a specific condition is satisfied

I am working on an animation within my Angular application @Component({ selector: 'app-portfolio', templateUrl: 'portfolio.page.html', styleUrls: ['portfolio.page.scss'], animations: [ trigger('slideInOut&apo ...

Issue: The 'typeOf' function is not exported by the index.js file in the node_modules eact-is folder, which is causing an import error in the styled-components.browser.esm.js file in the node_modulesstyled

Every time I attempt to start running, there are issues with breaks in npm start (microbundle-crl --no-compress --format modern,cjs) I have attempted deleting node_modules and package-lock.json, then running npm i again but it hasn't resolved the pro ...

Trouble with Firebase Setup in Ionic 4+ Web Application

I'm currently trying to establish a connection between my ionic application and Firebase for data storage, retrieval, and authentication. Despite using the npm package with npm install firebase, I encountered an error message that reads: > [email& ...

Can someone guide me on identifying the type of object in React/Typescript?

Can anyone help me remove this unnecessary definition? const [nextLocation, setNextLocation] = useState(null) as any[]; I have been struggling with this in my React Router 6 project. I've looked through the documentation but haven't found a suit ...

How to utilize TypeScript fetch in a TypeScript project running on node with Hardhat?

During a test in my hardhat project on VSCode, I encountered the need to retrieve the metadata object of my NFT from a specified URL. Initially, I assumed I would have to import fs to read the URL. However, while typing out the method, I impulsively opted ...

Tips for incorporating auth0 into a vue application with typescript

Being a beginner in TypeScript, I've successfully integrated Auth0 with JavaScript thanks to their provided sample code. However, I'm struggling to find any sample applications for using Vue with TypeScript. Any recommendations or resources would ...

Is it possible to modify the variables in a SCSS file within an Angular 2 project?

Currently, I am working with Angular 2 using a SCSS style. My challenge is to retrieve data from a server in order to change a specific variable within the component's style - specifically a percentage value. You can view the SCSS and HTML code here. ...

Issue arises when attempting to use the useEffect hook in React with Typescript to reset states upon the component unmounting

I'm facing an issue with my useEffect cleanup when the component unmounts and setting states simultaneously. My code involves selecting a client by clicking on them and setting their ID to send in an API request: const setClient = () => { setC ...

Apologies, the module "@org-name/package-name" could not be located

I've hit a roadblock for the past few days. My goal is to create a new npm package that wraps an API I've been developing. When bundling the package, everything seems to be fine in the /dist folder. However, when attempting to test it in a front ...

Tips for avoiding the reconstruction of components in if-else conditions within Angular

After just joining the Angular4 club as a ReactJS developer, I find myself using basic conditional statements with a very simple condition. Take a look: <div class="app-component"> <app-country *ngIf="condition"></app-country> ...