I want to create a tab group that is defined in a child component, but where it's possible to create tabs (and their contents) from the parent component.
Content projection
Initially, I tried to use content projection.
Parent component
<child>
<mat-tab tab label="Hello">World!</mat-tab>
</child>
Child component
<mat-tab-group>
<mat-tab label="Some">Tab!</mat-tab>
<ng-content selector="[tab]"></ng-content>
</mat-tab-group>
But the content wasn't being rendered in the child because since the tabs aren't in a tab group, they're not being initialized. And like said here, ng-content
needs to know the contents of the projection at compile time, so it is suggested that we manually initialize the tabs, though with no useful explanation...
Manually adding the tabs to the tab group
This answer, though related to mat-table
, leaves a good idea to try for my case.
Child component
@ViewChild(MatTable) tabGroup!: MatTabGroup;
@ContentChildren(MatTab) tabs!: QueryList<MatTab>;
ngAfterContentInit() {
this.tabs.forEach(tab => this.tabGroup./*♂️*/);
}
However, the tab group doesn't support any methods related to changing the Tab list.
Combining *ngFor
with content projection
The only information every tab needs really is just the label and its content. We can avoid projecting the tab if we just project its inner contents. We just need the parent to send some meta-information down to the child.
child.component.ts
@Input() tabs!: {label: string, selector: string}[];
child.component.html
<mat-tab-group>
<!-- some default tabs -->
<mat-tab *ngFor="let tab of tabs" [label]="tab.label">
<ng-content [select]="tab.selector"></ng-content>
</mat-tab>
</mat-tab-group>
parent.component.ts
tabs = [{label: "Hello", selector: "hello"}];
parent.component.html
<child [tabs]="tabs">
<p hello>World!</p>
</child>
To be clear, the tabs.selector
property will be used by the parent component to mark the content to project, and by the child component's ng-content
. Since it must be kebab-case, it has to be independent from label
.
I thought this solution worked, but it turned out it just worked if there were only 2 tabs.
Any other suggestions?