Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular wrapping angular material tabs component with in custom components

What I am trying to achieve here is I wanna wrap the angular material tabs component with in my shared components.

So, here is the component that I'm trying to wrap:

PS: I can display component in each tab:

<mat-tab-group>
  <mat-tab label="First"> Content 1 </mat-tab>
  <mat-tab label="Second"> Content 2 </mat-tab>
  <mat-tab label="Third"> Content 3 </mat-tab>
</mat-tab-group>

So, I wanna use wrapper for it to use it like:

<app-wrapper>
  <app-item>
    <app-item-header title="first"></app-item-header>
    <app-item-content>
      <app-needs-to-be-displayed></app-needs-to-be-displayed>
    </app-item-content>
  </app-item>
</app-wrapper>

app-wrapper.html

<mat-tab-group>
  <ng-content></ng-content>
</mat-tab-group>

no changes in the TS class

app-item.html

<mat-tab>
    <ng-content></ng-content>
</mat-tab>

no changes in the TS class

app-item-header.html

<ng-template mat-tab-label>
  {{title}}
</ng-template>

app-item-header.ts class

@Input() title:string = ''

app-item-content.html

<div>
   <ng-content></ng-content>
</div>

no changes in the TS class and this can hold an actual component

This gives no error in the console but nothing appears on the page too.

and here is working stackblitz version

https://stackblitz.com/edit/angular-wrapping-component-with-ng-content

PS : Please advice guys if this is the best solution to achieve this or there is another solution ?

like image 358
Un1xCr3w Avatar asked Oct 15 '22 20:10

Un1xCr3w


1 Answers

You don't see anything because the mat-tab components need to be direct children of the mat-tab-group component else it is not aware that any tabs exist. For this reason you need to approach things differently. You need to get the content from your custom components and then render everything inside the wrapper component. I think the simplest way is to get the content as templates and it is also very close to what you created. I will start with the lowest components and move up.

The app-item-content component can be as you created it. The app-item-header needs to be changed. Like for the tabs also the mat-tab-label directive needs to be applied directly inside the mat-tab-group. That is why I removed it from the template.

<ng-template>
  {{title}}
</ng-template>

The main change is that I expose the ng-template inside the component also as a property in the code using the ViewChild decorator. This enables the access of the template also from the outside the component.

@Component({
  selector: 'app-item-header',
  templateUrl: './item-header.component.html',
  styleUrls: ['./item-header.component.css']
})
export class ItemHeaderComponent implements OnInit {
  @ViewChild(TemplateRef) public headerTemplate: TemplateRef<any>;
  @Input() title:string = ''
}

After you can move to the app-item component. Here there is also just a small change in the template. Using the same approach as in the header component you can get the template for the item content.

<ng-template>
    <ng-content select="app-item-content"t></ng-content>
<ng-template>

In code you can then again use the ViewChild decorator to get the content template as before. The header is a child component of the item to get it you need to use a different but similar ContentChild decorator to get access to the header component which provides the header template.

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
  @ViewChild(TemplateRef) public contentTemplate: TemplateRef<any>;
  @ContentChild(ItemHeaderComponent) public itemHeader: ItemHeaderComponent;
}

At this point you have access to everything you need to render the contents inside the mat-tab components. In the template you need to render the mat-tab-group and a mat-tab corresponding to each app-item component and the contents from the templates that were exposed before.

<mat-tab-group>
  <mat-tab *ngFor="let item of appItems">
    <ng-template mat-tab-label>
    <ng-container *ngTemplateOutlet="item.itemHeader.headerTemplate"></ng-container>
    </ng-template>
    <ng-container *ngTemplateOutlet="item.contentTemplate"></ng-container>
  </mat-tab>
</mat-tab-group>

As you can see it is quite straight forward you create a tab for each item and render inside the contents from the item. A bit of a tricky thing is the header you need to render the template inside another ng-template to be able to add the mat-tab-label directive that it is recognized as header content. To get all the app-item components inside the wrapper component you use the ContentChildren decorator which gives a list of components inside the content. Like in previous examples we just add a property in code the rest is handled by Angular.

@Component({
  selector: 'app-wrapper',
  templateUrl: './wrapper.component.html',
  styleUrls: ['./wrapper.component.css']
})
export class WrapperComponent implements OnInit {
  @ContentChildren(ItemComponent) public appItems: QueryList<ItemComponent>;
}

I have created also a fork of you StackBlitz where you can see it working.

like image 145
AlesD Avatar answered Oct 18 '22 17:10

AlesD