Components in Angular 4 that are loaded dynamically using attribute directives are enclosed within a <div> element

My goal is to dynamically generate components based on the configuration options, specifically creating a toolbar with different "toolbar items".

Following the Angular guide at: https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html, I have implemented a dedicated directive:

@Directive({
    selector: '[mte-toolbar-item-host]'
})
export class ToolbarItemDirective {

    constructor(public viewContainerRef: ViewContainerRef) {
    }
}

This directive will serve as the insertion point for the components.

I also created the main ToolbarComponent:

@Component({
    selector: "mte-toolbar",
    templateUrl: "./toolbar.component.html",
    styleUrls: ["./toolbar.component.css"],
    providers: [ToolbarService]
})
export class ToolbarComponent implements AfterViewInit {
    toolbar: Toolbar;

    @ViewChild(ToolbarItemDirective)
    toolbarItemHost: ToolbarItemDirective;

    constructor(private toolbarService: ToolbarService, private componentFactoryResolver: ComponentFactoryResolver) {
    }

    ngAfterViewInit(): void {
        this.toolbar = new Toolbar(this.toolbarService.getToolbarItems());
        for (let i in this.toolbar.items) {
            let toolbarItem = this.toolbar.items[i];
            let toolbarItemFactory = this.componentFactoryResolver.resolveComponentFactory(toolbarItem.componentType);
            let componentRef = this.toolbarItemHost.viewContainerRef.createComponent(toolbarItemFactory);
            componentRef.instance.toolbarItem = toolbarItem;
        }
    }
}

The template for the ToolbarComponent includes an unordered list with injection points:

<ul class="nav nav-pills">
    <li role="presentation" mte-toolbar-item-host></li>
</ul>

The toolbarService currently provides a list of default components:

... 

GlyphToolbarItem and GlyphToolbarItemComponent classes were implemented to handle the toolbar items and their templates respectively. Here's how they are structured:

...

I anticipated the output to be rendered correctly within list elements, but instead, the dynamic components are wrapped in <div>. Is this behavior correct or is there a better way to achieve this?

Answer №1

It appears that you need to use GlyphToolbarItemComponent:

@Component({
    selector: 'li[mte-glyph-toolbar-item]',
    template: '<a class="btn btn-default btn-sm glyphicon {{toolbarItem.glyphIcon}}"></a>'
})
export class GlyphToolbarItemComponent {
  @HostBinding('attr.role') role ="presentation";
  toolbarItem: ToolbarItem;
}

Additionally, you should remove the empty list item and move mte-toolbar-item-host to a ng-container within a ul element.

<ul class="nav nav-pills">
  <ng-container mte-toolbar-item-host></ng-container>
</ul>

It is also worth noting that Angular may not approve of creating these components in ngAfterViewInit.

I have provided an example here: https://plnkr.co/edit/8cjlzooO1Oh2CWydxbSN?p=info

Answer №2

Regardless of the specific element you retrieve the ViewContainerRef reference from, createComponent will insert a sibling and utilize the selector of the dynamically generated component as the element. In cases where no element is specified in the selector, <div> is used by default.

If you modify the selector from

selector: '[mte-glyph-toolbar-item]',

to

selector: 'li[mte-glyph-toolbar-item]',

or

selector: 'li',

you will receive an <li> element.

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

Visibility of an Angular 2 directive

It's frustrating that I can't change the visibility of a reusable directive in Angular2. 1) Let's say I have a login page. I want to control the visibility of my navbar based on whether I am on the login page or logged in. It should be hid ...

Encountering a problem when utilizing window.ethereum in Next Js paired with ether JS

Experiencing some difficulties while utilizing the window.ethereum in the latest version of NextJs. Everything was functioning smoothly with NextJs 12, but after upgrading to NextJs 13, this error started popping up. Are there any alternative solutions ava ...

Invoking a functionality within a stream of events through an observable's subscribe

Service2.ts public flags$: BehaviorSubject<FlagName> = new BehaviorSubject<FlagName>("custom-flag-1"); This flag is set up as follows: private _setFlags = () => { const flagsData = this._customClient.getFlags(); if (f ...

What is the best way to dynamically include route paths in URL in Angular 2?

When exploring a listing on the detail page, I have various options available such as: 1) option 2) option 1 3) option 2 4) option 3 Each option leads to the same detail page but with unique content. I want the user's choice to be reflected in the ...

Mastering TypeScript in Router Configuration

I am currently working with a standard router setup. type Routes = '/' | '/achievements' | ... ; This helps in identifying the routers present in the project. However, I am faced with a new challenge of creating an array that includes ...

Having trouble with browser freezing when using array push in Angular 4? Looking for a solution to fix this issue?

In my logsCompoment.ts file, there is an array called logs where I store new log entries and display them on the HTML page using ngFor directive. I achieve this by handling incoming data in the following manner: this.socket.on('newline', (data) ...

Utilizing Angular 2 for transforming JSON data into Angular classes

I have been working through the Angular2 getting started guide to kickstart my own application development. So far, I have managed to retrieve JSON objects from a local file and display them in angular2 templates. However, the challenge arises when the str ...

Mastering the Conversion from NGRX Effect to NGRX Effect v15

I am currently working on converting the code snippet to NGRX 15. As a newcomer to Angular, I could use some guidance. "@ngrx/effects": "^15.4.0" @Injectable() export class SnackbarEffects { @Effect({ dispatch: false }) cl ...

I'm struggling to grasp the utilization of generics within the http.d.ts module in Node.js code

type RequestHandler< Request extends **typeof IncomingMessage = typeof IncomingMessage**, Response extends **typeof ServerResponse = typeof ServerResponse**, > = (req: InstanceType<Request>, res: InstanceType<Response> ...

Angular Pipe -- Implicit type 'any' error occurs when trying to index type 'string' with an expression

Encountering an error while constructing the time ago pipe: Obtaining an implicit 'any' type due to inability to index type with a 'string' expression if (value) { const seconds = Math.floor( (+new Date() - +new Date(Numb ...

What is the method to incorporate a fresh generic parameter without officially announcing it?

My goal is to define a type union where one of the types extends an existing type: // The original type type Foo<V> = { value: V; onChange: (value: V) => void }; // Type union incorporating Foo type ADT = ({ kind: "foo" } & Foo<a ...

Tips for accessing properties in JSON objects using AngularJS

In my Angular project, I have a TypeScript class called CheckoutInfo. export class CheckoutInfo { lines: CheckoutInfoLine[]; taxRate: number; get subTotal(): number { return this.lines.reduce((acc: number, cur: CheckoutInfoLine) => ...

Passing data to a child component using Context in React is not working

I have a parent component where I am storing data in an array of objects and then passing it to another component through Context. Here is how my parent component looks: export type HistoryData = { input: string; date: string; ...

Creating Concurrent Svelte Applications with Local State Management

Note: Self-answer provided. There are three primary methods in Svelte for passing data between components: 1. Utilizing Props This involves passing data from a parent component to a child component. Data transfer is one-way only. Data can only be passed ...

Leveraging async pipe along with ngIf

Given the status property defined and initialized as shown below: public status:Promise<String>; constructor() { this.status = this.getStatus(); } public getStatus():Promise<String>{ return new Promise((resolve,reject)=>{ ...

The click event is activated following the :active selector being triggered

My Angular application features a button with a slight animation - it moves down by a few pixels when clicked on: &:active { margin: 15px 0 0 0; } The button also has a (click)="myFunction()" event listener attached to it. A common issue arises w ...

Retrieve the encrypted URL

I'm facing an issue with extracting parameters from an encrypted URL. When using the queryparams function, it only retrieves a portion of the URL after being decrypted. For instance, consider this example URL: http://localhost:4200/househouse? MGRjYjQ ...

The response from my reducer was a resounding "never," causing selectors to be unable to accurately detect the state

Currently utilizing the most recent version of react. I am attempting to retrieve the state of the current screen shot, but encountering an error indicating that the type is an empty object and the reducer is "never". I am unable to detect the state at all ...

Error message: The object is not visible due to the removal of .shading in THREE.MeshPhongMaterial by three-mtl-loader

Yesterday I posted a question on StackOverflow about an issue with my code (Uncaught TypeError: THREE.MTLLoader is not a constructor 2.0). Initially, I thought I had solved the problem but now new questions have surfaced: Even though I have installed &apo ...

Unable to fulfill the pledge

I'm struggling to receive the promise from the backend after making a get request. Can anyone help me figure out what I might be doing wrong? makeLoginCall(_username: string, _password: string) { let promise = new Promise((resolve, reject) => ...