Kamil Chaber
Kamil Chaber

Reputation: 578

Angular 4 dynamically loaded components using attribute directive are wrapped with div

I'm trying to dynamically create some components based on the configuration options. To be more specific I want to have some toolbar, which can contain different "toolbar items".

As per angular guide: https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html I 've created dedicated directive

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

    constructor(public viewContainerRef: ViewContainerRef) {
    }
}

which will be used to indicate the "injection" point for the components.

I have 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;
        }
    }
}

with appropriate template

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

For now, the toolbarService is returning the following list of the components:

export const DEFAULT_TOOLBAR_ITEMS: ToolbarItem[] = [
    new GlyphToolbarItem(GlyphToolbarItemComponent, "example-component-id-1", null, "glyphicon-log-in"),
    new GlyphToolbarItem(GlyphToolbarItemComponent, "example-component-id-2", null, "glyphicon-floppy-remove"),
    new GlyphToolbarItem(GlyphToolbarItemComponent, "example-component-id-3", null, "glyphicon-log-in"),
    new GlyphToolbarItem(GlyphToolbarItemComponent, "example-component-id-4", null, "glyphicon-paste")
];

@Injectable()
export class ToolbarService {

    getToolbarItems(): ToolbarItem[] {
        return DEFAULT_TOOLBAR_ITEMS;
    }

}

GlyphToolbarItem:

export class GlyphToolbarItem extends ToolbarItem {
    glyphIcon: string;

    constructor(componentType: Type<any>, id: string, command: AbstractCommand, glyphIcon: string) {
        super(componentType, id, command);
        this.glyphIcon = glyphIcon;
    }
}

GlyphToolbarItemComponent:

@Component({
    selector: '[mte-glyph-toolbar-item]',
    templateUrl: './glyph-toolbar-item.component.html'
})
export class GlyphToolbarItemComponent {
     toolbarItem: ToolbarItem;
}

its template

<a class="btn btn-default btn-sm glyphicon {{toolbarItem.glyphIcon}}"></a>

I was expecting to receive something like:

<ul _ngcontent-c2="" class="nav nav-pills"><!---->
    <li role="presentation" mte-glyph-toolbar-item="" ng-version="4.0.3">
        <a class="btn btn-default btn-sm glyphicon glyphicon-log-in"></a>
    </li>
    <li role="presentation" mte-glyph-toolbar-item="" ng-version="4.0.3">
        <a class="btn btn-default btn-sm glyphicon glyphicon-floppy-remove"></a>
    </li>
    <li role="presentation" mte-glyph-toolbar-item="" ng-version="4.0.3">
        <a class="btn btn-default btn-sm glyphicon glyphicon-log-in"></a>
    </li>
    <li role="presentation" mte-glyph-toolbar-item="" ng-version="4.0.3">
        <a class="btn btn-default btn-sm glyphicon glyphicon-paste"></a>
    </li>
</ul>

but instead I've received the dynamic components wrapped with the <div>:

<ul _ngcontent-c2="" class="nav nav-pills"><!---->
<div mte-glyph-toolbar-item="" ng-version="4.0.3">
    <a class="btn btn-default btn-sm glyphicon glyphicon-log-in"></a>
</div>
<div mte-glyph-toolbar-item="" ng-version="4.0.3">
    <a class="btn btn-default btn-sm glyphicon glyphicon-floppy-remove"></a>
</div>
<div mte-glyph-toolbar-item="" ng-version="4.0.3">
    <a class="btn btn-default btn-sm glyphicon glyphicon-log-in"></a>
</div>
<div mte-glyph-toolbar-item="" ng-version="4.0.3">
    <a class="btn btn-default btn-sm glyphicon glyphicon-paste"></a>
</div>

Why am I receiving the dynamic components wrapped with the <div> instead of the <li> element (which seems to be totally ignored)? Is this the proper way to achieve this?

Upvotes: 0

Views: 1692

Answers (2)

Julia Passynkova
Julia Passynkova

Reputation: 17899

I think you have to 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;
}

and remove empty li + move mte-toolbar-item-host to ul>ng-container

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

Also I think Angular won't like that you create these components in ngAfterViewInit

I created https://plnkr.co/edit/8cjlzooO1Oh2CWydxbSN?p=info

Upvotes: 1

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 658087

It doesn't matter what element it is you get the ViewContainerRef element from. createComponent will add a sibling and use the selector of the dynamically created component as element. Because no element is given in the selector <div> is used by default.

If you change the selector from

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

to

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

or

selector: 'li',

you will get a <li> element.

Upvotes: 1

Related Questions