I'm trying to factor out popup menus so I can write this:
<panel-menu>
<panel-menu-item>Edit input</panel-menu-item>
<panel-menu-item>Edit mappings</panel-menu-item>
<panel-menu-item divider-before>Show agent code</panel-menu-item>
</panel-menu>
I have a panel-menu
component with this HTML:
<div class="btn-group" [class.open]="...">
<button type="button" class="btn btn-default" (click)="..."><i class="ion-gear-b icon-lg"></i></button>
<ul class="dropdown-menu dropdown-menu-right">
<ng-content select="panel-menu-item"></ng-content>
</ul>
</div>
and panel-menu-item
with this HTML:
<li *ngIf="dividerBefore" class="divider"></li>
<li><a><ng-content></ng-content></a></li>
The problem is that the resulting DOM has a panel-menu-item
between the ul
and the li
, which breaks the third-party CSS.
Is there some way to project just the content of the selected children, and not the children themselves?
This answer suggests to use an attribute on the li
instead of a component, but that leaks implementation. Users of panel-menu
shouldn't need to know what elements menu items are implemented as.
To project just content you should wrap your content in embedded view like:
panel-menu-item.component.ts
@Component({
selector: 'panel-menu-item',
template: `
<ng-template>
<li *ngIf="dividerBefore" class="divider"></li>
<li><a><ng-content></ng-content></a></li>
</ng-template>
`
})
export class PanelMenuItemComponent {
@ViewChild(TemplateRef) content: TemplateRef<any>;
}
In the preceding code i'm wrapping template in ng-template
tag and getting TemplateRef
instance from it by using @ViewChild
decorator.
Having TemplateRef
we can easily manage where to insert the template:
panel-menu.component.ts
@Component({
selector: 'panel-menu',
template: `
<div class="btn-group" >
<button type="button" class="btn btn-default">Some button</button>
<ul class="dropdown-menu dropdown-menu-right">
<ng-container *ngFor="let item of menuItems">
<ng-container *ngTemplateOutlet="item.content"></ng-container>
</ng-container>
</ul>
</div>
`
})
export class PanelMenuComponent {
@ContentChildren(PanelMenuItemComponent) menuItems: QueryList<PanelMenuItemComponent>;
constructor(private cdRef: ChangeDetectorRef) {}
ngAfterViewInit() {
this.cdRef.detectChanges();
}
}
I'm using @ContentChildren
to get hold of our panel-menu items and then just use built-in directive NgTemplateOutlet to place content inside ul
.
We have to run the second digest cycle by using this.cdRef.detectChanges();
because we'll get error
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'.
as soon as @ViewChild(TemplateRef) content
updates its value during change detection.