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

Handling routerLink exceptions in Angular 2, 4, and 5

In my app, I am working on catching routing exceptions. There are three ways in which a user can navigate: If the user types the address directly - this can be caught easily by using { path: '**', redirectTo: 'errorPage'} in the route ...

Setting up the viewport addon in Angular can be done by following these steps

After checking this documentation, I attempted to add a custom viewport in the config.js file: import { setParameters } from '@storybook/angular'; // switching from react to angular import { INITIAL_VIEWPORTS } from '@storybook/addon-viewpo ...

Steps to combine NativeScript and Angular CLI

Exploring the potential of integrating NativeScript with Angular CLI to develop applications for both web and native mobile platforms. I attempted to use Nathan Walker's NativeScript Magic, but encountered difficulties creating a fresh application wit ...

Custom type declaration file in Typescript fails to function properly

I have searched through countless solutions to a similar issue, but none seem to work for me. I am attempting to utilize an npm package that lacks TypeScript type definitions, so I decided to create my own .d.ts file. However, every time I try, I encounter ...

Angular 4 seems to be experiencing some issues with the EventEmiitter functionality

Hey everyone, I'm new to working with Angular 4 and I've been trying to implement the event emitter concept without success. Here's the code I have in my demo: app.component.ts import { Component } from '@angular/core'; @Compon ...

I'm struggling to find a solution to this pesky TypeScript error that keeps popping up in the button component's styling. How can

An error related to style is appearing: <Button style = No overload matches this call. Overload 1 of 3, '(props: { href : string; } & { children?: React Node; classes?: Partial<Button Classes> | undefined; color?: "primary" | ...

In Angular/Typescript, dynamically add a class to a `td` element when it is clicked. Toggle the class on and off

My problem arises when trying to individually control the arrow icons for each column in my COVID-19 data table. By using Angular methods, I aim to show ascending and descending arrows upon sorting but run into the challenge of changing arrows across all c ...

Using React to update the state of an array of objects

I'm faced with a challenge in changing the properties of an object within an array of objects at a specific index using a function: const handleEdit= (index) =>{ if(itemList[index].edit==true){ const copied=[...itemList]; const item2 = {...ite ...

Headers cannot be modified after they have been sent to the client in Node.js and Angular

I am working on developing login and registration services using Nodejs Express. Every time I make a request in postman, I consistently encounter the same error: https://i.stack.imgur.com/QZTpt.png Interestingly, I receive a response in postman (register ...

Using LINQ with ngFor in Angular 6

Within the component.ts, I extract 15 different lookup list values and assign each one to a list, which is then bound to the corresponding <select> element in the HTML. This process functions correctly. Is there a method to streamline this code for ...

Option to modify the arguments of a method in a parent class

I encountered an interesting problem recently. I have two classes: class Animal { public talk() { console.log('...'); } } and class Dog extends Animal { public talk(noise: string) { console.log(noise); super.talk() } } The i ...

Trying out Angular2 service using a fabricated backend

Just a heads up: I know Angular2 is still in alpha and undergoing frequent changes. I am currently working with Angular2 and facing an issue with testing an injectable service that has a dependency on http. I want to test this service using a mock backend ...

What could be causing my vis.js network's node hover popups to not function properly?

I've encountered an issue where, despite adding the 'title' property to my node objects, the pop up window with the title content doesn't appear when I hover over a node. Here are the options I've chosen and how I've set up m ...

Ways to transfer selected options from a dropdown menu to a higher-level component

I am currently in the process of configuring a modal component that showcases various data from a specific record to the user. The user is provided with a Bulma dropdown component for each field, enabling them to make changes as needed. To streamline the c ...

What is the best way to execute a function on the output of *ngFor directive in Angular 2?

Imagine having a list of all the users within your system: allUsers = { a: {name:'Adam',email:'<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="39585d5854794d5c4a4d5a56175a56... f: {name:'fred' ...

Variety of part ingredients

In my component, I have a button and include another component which also contains a button. How can I align these two buttons next to each other without using absolute positioning? When I try positioning them using absolute right and top values, the lay ...

Angular Material's dialog modal swiftly closes without delay

Could you please explain why the modal opens and then closes instantly when I click on the Create Project button? https://example.com/edit/angular-code I am trying to display a component within the modal using Angular Material. portafolio.component.ts ...

Is it possible to utilize a FOR loop in TypeScript to store an array in a variable?

Hey there pals! I could really use your brain power for a solution that requires some context. Our array ress is limited to items that meet a certain condition. After filtering the array, I need to store the new results in a different variable. I' ...

The latest update of Google Maps Javascript API Version 3.54 is causing compatibility issues with Angular Google Maps 13.2

Since Nov 16, 2023, a concerning issue with Google Maps has emerged in my Angular Application. The problem involves receiving 403 Errors from Google Maps and the maps failing to load (resulting in a blank page), despite having a valid API Key. The followi ...

Choose from the Angular enum options

I am working with an enum called LogLevel that looks like this: export enum LogLevel { DEBUG = 'DEBUG', INFO = 'INFO', WARNING = 'WARNING', ERROR = 'ERROR' } My goal is to create a dropdown select el ...