question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to wrap component which uses ng-content

See original GitHub issue

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:

@Component({
  selector: 'my-accordion',
  template: `
    <ngb-accordion>
      <ng-content></ng-content>
    </ngb-accordion>
  `
})
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

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:3
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
siovenecommented, Dec 20, 2018

I expanded upon @mcelotti’s solution and successfully wrapped the accordion:

import {
    Component,
    ContentChildren,
    EventEmitter,
    Input, OnInit,
    Output,
    QueryList,
    TemplateRef,
    ViewChild
} 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;

@Component({
    selector: "app-accordion-panel",
    template: "<ng-template #innerTemplate><ng-content></ng-content></ng-template>"
})
export class AccordionPanelComponent {
    @ViewChild("innerTemplate")
    public innerTemplate: TemplateRef<any>;

    @Input()
    public disabled = false;

    @Input()
    public id = `accordion-panel-${nextId++}`;

    @Input()
    public title: string;

    @Input()
    public type: string;
}

@Component({
    selector: "app-accordion",
    templateUrl: "./accordion.component.html"
})
export class AccordionComponent implements OnInit {
    @ViewChild("innerAccordion")
    public innerAccordion: NgbAccordion;

    @ContentChildren(AccordionPanelComponent)
    public panels: QueryList<AccordionPanelComponent>;

    @Input()
    public activeIds: string | string[] = [];

    @Input()
    public closeOthers: boolean;

    @Input()
    public destroyOnHide = true;

    @Input()
    public type: string;

    @Output()
    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() {
        this.innerAccordion.expandAll();
    }

    public collapse(panelId: string) {
        this.innerAccordion.collapse(panelId);
    }

    public collapseAll() {
        this.innerAccordion.collapseAll();
    }

    public toggle(panelId: string) {
        this.innerAccordion.toggle(panelId);
    }
}

And the HTML:

<ngb-accordion
    #innerAccordion
    [activeIds]="activeIds" [closeOthers]="closeOthers" [destroyOnHide]="destroyOnHide" [type]="type">
    <ngb-panel
            *ngFor="let panel of panels"
            [disabled]="panel.disabled"
            [id]="panel.id"
            [title]="panel.title"
            [type]="panel.type">
        <ng-template ngbPanelContent>
            <ng-template [ngTemplateOutlet]="panel.innerTemplate"></ng-template>
        </ng-template>
    </ngb-panel>
</ngb-accordion>
1reaction
mcelotticommented, Nov 27, 2018

I’ve used @moccaplusplus hack and it works also with ngb-tabset. But it’s a hack and therefore I’m testing the ngTemplateOutlet solution, here’s a demo: https://stackblitz.com/edit/angular-959kdu?file=app%2Fstepper.ts. Right now it’s working and I’m able to create my “wrapper” of ngb-tabset, but there might be some pitfalls

Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular ng-content and Content Projection: A Complete Guide ...
In this post, we are going to learn how to use this feature to design components that have a very simple but still...
Read more >
angular - How to conditionally wrap a div around ng-content
import { Component, Input } from '@angular/core'; @Component({ selector: 'div-wrapper', template: ` <div *ngIf="wrap; else unwrapped"> ...
Read more >
Angular Wrapping Component With Ng Content (forked)
Editor Preview Both. Sign in. Project. Search. Settings. Switch to Light Theme. Enter Zen Mode. Project. Download Project. Info. Angular Wrapping Component ......
Read more >
Content projection - Angular
This topic describes how to use content projection to create flexible, reusable components. To view or download the example code used in this...
Read more >
Everything you need to know about ng-template, ng-content ...
Angular wraps the host element (to which the directive is applied) inside ... Components that are used in published libraries make use of ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found