How to wrap component which uses ng-content

Bug description:

It’s not possible currently to wrap any component of the library which uses ng-content/transclusion. This seems be caused by @ContentChildren which can’t query 2+ levels of transcluded content.

So the following does not work:

  selector: 'my-accordion',
  template: `
export default class MyAccordionComponent {}

And used as <my-accordion><ngb-panel>Hello world<ngb-pabel></my-accordion> because ngb-panel can’t be queried by ngb-accordion. Maybe using { descendants: true } with @ContentChildren(NgbPanel) could solve this issue.

How would you suggest to wrap components?

Version of Angular, ng-bootstrap, and Bootstrap:

Angular: 2.4.5

ng-bootstrap: 1.0.0-alpha.19

Bootstrap: Bootstrap 4 alpha6

2 thoughts on “How to wrap component which uses ng-content

  1. Guys I think this is definitely not the problem of ng-bootstrap, it is a problem of Angular platform.
    Angular does not support ContentChildren queries deeper than one level (your wrapper component is second level), and it is so by the design.

    A great explanation can be found here: angular/angular#20810 (comment)

  2. I expanded upon @mcelotti‘s solution and successfully wrapped the accordion:

    import {
        Input, OnInit,
    } from "@angular/core";
    import {NgbAccordion, NgbPanelChangeEvent} from "@ng-bootstrap/ng-bootstrap";
    // tslint:disable-next-line:no-empty-interface
    export interface AccordionPanelChangeEvent extends NgbPanelChangeEvent {
    let nextId = 0;
        selector: "app-accordion-panel",
        template: "<ng-template #innerTemplate><ng-content></ng-content></ng-template>"
    export class AccordionPanelComponent {
        public innerTemplate: TemplateRef<any>;
        public disabled = false;
        public id = `accordion-panel-${nextId++}`;
        public title: string;
        public type: string;
        selector: "app-accordion",
        templateUrl: "./accordion.component.html"
    export class AccordionComponent implements OnInit {
        public innerAccordion: NgbAccordion;
        public panels: QueryList<AccordionPanelComponent>;
        public activeIds: string | string[] = [];
        public closeOthers: boolean;
        public destroyOnHide = true;
        public type: string;
        public panelChange = new EventEmitter<AccordionPanelChangeEvent>();
        public ngOnInit() {
            this.innerAccordion.panelChange.subscribe((event: NgbPanelChangeEvent) => {
                this.panelChange.emit(event as AccordionPanelChangeEvent);
        public isExpanded(panelId: string): boolean {
            return this.innerAccordion.isExpanded(panelId);
        public expandAll() {
        public collapse(panelId: string) {
        public collapseAll() {
        public toggle(panelId: string) {

    And the HTML:

        [activeIds]="activeIds" [closeOthers]="closeOthers" [destroyOnHide]="destroyOnHide" [type]="type">
                *ngFor="let panel of panels"
            <ng-template ngbPanelContent>
                <ng-template [ngTemplateOutlet]="panel.innerTemplate"></ng-template>