Creating custom components that encapsulate the functionality of Angular Material tabs component

I am aiming to incorporate the Angular Material tabs component within my shared components.

Here is the component I'm attempting to wrap:

Note: Each tab can display a component:

<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>

My goal is to create a wrapper for it to be used like this:

<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

No errors are shown in the console, but nothing appears on the page either.

View the working StackBlitz version here:

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

Note: Please provide feedback on whether this is the best solution or if there is another approach?

Answer №1

When attempting to view the content, you may encounter difficulties if the mat-tab components are not direct children of the mat-tab-group component. In this case, the tabs may not be recognized. To address this issue, a different approach is required. The recommended method involves extracting the content from custom components and then organizing it within a wrapper component. One effective strategy is to extract the content as templates, which closely aligns with your current setup. Begin by focusing on the lower-level components before progressing upwards.

The existing app-item-content component can remain unchanged. However, adjustments are needed for the app-item-header. Similar to tab elements, the mat-tab-label directive must be directly applied within the mat-tab-group, prompting its removal from the template.

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

A significant modification involves exposing the ng-template within the component as a property in the code through the use of the ViewChild decorator. This allows external access to the template within 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 = ''
}

Moving towards the app-item component, minor template adjustments are necessary. By adopting a similar approach to the header component, you can retrieve the template for the item content.

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

In the code, utilize the ViewChild decorator to obtain the content template as done previously. Since the header is a child component of the item, a slightly different yet analogous approach using the ContentChild decorator allows access to the header component and its corresponding 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;
}

With all essential components accessible, one can proceed to render the contents within the mat-tab components. Within the template, include the mat-tab-group alongside a mat-tab for each app-item component, utilizing the exposed templates for rendering.

<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>

The process involves creating a tab for each item and populating them with content from the respective items. Notably, when handling headers, embedding the template within another ng-template ensures proper recognition as header content with the mat-tab-label directive included. To incorporate all app-item components within the wrapper component, leverage the ContentChildren decorator, which generates a list of contained components. Simply introduce a corresponding property in the code, allowing Angular to manage the rest effortlessly.

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

An example showcasing the functionality can be found in a modified version of your StackBlitz, available at this link.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The jsPDF html() method is incorrectly splitting the HTML element

I have been working on creating an invoice report using jsPDF in an Angular 7 app. The report is text-based as images are not accepted by the client. In my code, I have a main div with the id "report" which I retrieve and pass to the .html() function. With ...

Tips for simulating a Ref

I have a Vue3 component where, within the setup(), I have defined the following function: const writeNote(note: Ref<Note>) => { console.log(`note ${note.id}`) } This function takes a Ref<Note>, with Note being an Interface. There are two s ...

Angular HTTP Requests with Observables

In order to enhance the security of my Angular web application, I am currently in the process of developing a TypeScript method that will validate a JWT token on the server using an HTTP call. It is imperative for me that this method returns a boolean val ...

Tips for triggering functions when a user closes the browser or tab in Angular 9

I've exhausted all my research efforts in trying to find a solution that actually works. The problem I am facing is getting two methods from two different services to run when the browser or tab is closed. I attempted using the fetch API, which worke ...

Place text in the center of a div and add a numbered circle beside it

I need help adding a circle with a number next to a div containing centered text. Take a look at the image I have attached for reference. https://i.sstatic.net/tHA65.png I believe I am close to achieving the desired outcome, but I am struggling to center ...

What is the best option: rewriting conditions and rules in Virtual Host or .htaccess file?

Currently, I am utilizing apache 2.4 to host an angular2 application. Initially, I defined the rewrite rules and conditions within my virtual host configuration. However, now I'm contemplating whether it would be more advantageous to employ an .htacce ...

Upgrading from Angular 6 to Angular 7

After upgrading my Angular 4 app to Angular 6, I'm now looking to make the jump to Angular 7. According to a recent article, running the command below should only take around 10 minutes for the upgrade process: ng update @angular/cli @angular/core F ...

What causes a compilation error to occur when a mapped type is invoked inside a class?

Below is a code snippet for review. An error occurs when calling the get method within the class, but works fine when called outside. Any thoughts on why? type DefinedKeys<T> = keyof { [K in keyof T as undefined extends T[K] ? never : K]: K } cla ...

Issue encountered while attempting to install Angular CLI on Windows: ENOENT Error

After extensive research online, I have been struggling to find a solution to this issue. I've attempted the following command: npm install -g @angular/cli The error message I'm receiving is: Npm version: 7.7.6 Node version: 15.13.0 Update: P ...

Assigning a value to an Angular class variable within the subscribe method of an HTTP

Understanding the inner workings of this process has been a challenge for me. I've come across numerous articles that touch on this topic, but they all seem to emphasize the asynchronous nature of setting the class variable only when the callback is t ...

React.js: You cannot call this expression. The type 'never' does not have any call signatures

Could someone help me troubleshoot the error I'm encountering with useStyles? It seems to be related to Typescript. Here's the line causing the issue: const classes = useStyles(); import React from "react"; import { makeStyles } from & ...

Array containing HTML code for Ionic framework

Just starting out with Ionic/Angular and I have a question. How can I display an array containing HTML content? { "code" : "06", "descr" : "Some text:</br>Other text.<br/>Other text</br>Other text." } When I try to print it, the HTML ta ...

Encountering ReferenceError when attempting to declare a variable in TypeScript from an external file because it is not defined

Below is the typescript file in question: module someModule { declare var servicePort: string; export class someClass{ constructor(){ servicePort = servicePort || ""; //ERROR= 'ReferenceError: servicePort is not defined' } I also attempted t ...

Flipping the Observable List in Angularfire2

Currently, I have implemented the following code to reverse the list: this.items = this.db.list('/privacy').map( (array) => {return array.reverse()} ) as FirebaseListObservable<any[]>; When displaying the list, I call it like this: &l ...

Tips for attaching to a library function (such as Golden Layout) and invoking extra functionalities

I am currently utilizing a library named Golden Layout that includes a function called destroy, which closes all application windows on window close or refresh. My requirement is to enhance the functionality of the destroy function by also removing all lo ...

Error encountered during Angular update from version 8 to 9 - yarn compatibility issue

I recently upgraded my project from Angular version 8 to 9 following the guidelines on angular.io. However, after the upgrade, I encountered an error while trying to run yarn install. Can anyone help me resolve this issue? Error: error @angular-devkit/ ...

My custom styles no longer seem to be applying after upgrading Angular Material from version 14 to 15. What could be causing this issue

Having some challenges with the migration from Angular 14 to Angular 15 when it comes to overriding material UI elements and themes. Looking for blog posts or documentation that can provide guidance on how to smoothly transition. Specifically, experiencin ...

The scrollTo() function does not function properly on iOS devices

When I try to scroll to my category list, the command works on Android but not on iOS. Here is a code example: @ViewChild(Content) content: Content; scrollTo() { this.content.scrollTo(0, 100, 200); } - <button ion-button (click)="scrollTo()"> ...

Suggestions for improving string.replace across various attributes

I have been working on an application with an editable script feature. As I go through the script, I find myself needing to replace placeholders with local data. While this process is functional, it feels quite messy. initScript(script: LegalScript, lead: ...

Unit Testing with Angular: Testing the setValueControl function

I am currently in the process of writing unit tests for a straightforward function that assigns controls to various values. fillFormAssociazioneVeicolo() { if (this.aaa) { setValueControl( this.aaa.targaTelaio, this.form.get(&apos ...