Robert Gruner
Robert Gruner

Reputation: 139

Angular Material Tabs not working with wrapper component

We are developing a corporate component library which should provide Material Designed Angular Components. So the users of this library are not supposed to use e.g. Angular Material directly but rather include some component like "custom-tabs".

Using the components of MatTabModule directly works like a charm, whereas when using our custom components the projected content does not show up.

Usage looks very similar to the Angular Material API:

<custom-tabs>
  <custom-tab [label]="labelA">Content A</custom-tab>
  <custom-tab [label]="labelB">Content B</custom-tab>
  <custom-tab [label]="labelC">Content C</custom-tab>
</custom-tabs>

The custom components try to project the content as follows:

<!-- custom-tabs template -->
<mat-tab-group>
  <ng-content></ng-content>
</mat-tab-group>

<!-- custom-tab template -->
<mat-tab [label]="label">
  <ng-content></ng-content>
</mat-tab>

Does anyone have an idea how we can get it working?

I provided a StackBlitz where you can see the problem in action.

Upvotes: 14

Views: 9074

Answers (5)

Guest
Guest

Reputation: 11

const customTabDebugElements = fixture.debugElement.queryAll(By.directive(MockCustomTabComponent));
const customTabs = customTabDebugElements.map(debugElement => debugElement.componentInstance as MockCustomTabComponent);

// Simulate the ContentChildren query result
component.tabs = new QueryList<CustomTabComponent>();
component.tabs.reset(customTabs);

// Call the lifecycle hook manually
component.ngAfterViewInit();

Upvotes: 1

ADITHYA D
ADITHYA D

Reputation: 11

it('should initialize tabGroup with tabs from ContentChildren', () => {
    const customTabDebugElements = fixture.debugElement.queryAll(By.directive(MockCustomTabComponent));
    const customTabs = customTabDebugElements.map(debugElement => debugElement.componentInstance as MockCustomTabComponent);
    
    // Simulate the ContentChildren query result
    component.tabs = new QueryList<CustomTabComponent>();
    component.tabs.reset(customTabs);
    
    // Call the lifecycle hook manually
    component.ngAfterViewInit();
    
    expect(component.tabGroup._tabs.length).toBe(customTabs.length);
  });

  it('should call ngAfterContentInit on tabGroup after setting tabs', () => {
    const customTabDebugElements = fixture.debugElement.queryAll(By.directive(MockCustomTabComponent));
    const customTabs = customTabDebugElements.map(debugElement => debugElement.componentInstance as MockCustomTabComponent);
    
    spyOn(component.tabGroup, 'ngAfterContentInit');
    
    // Simulate the ContentChildren query result
    component.tabs = new QueryList<CustomTabComponent>();
    component.tabs.reset(customTabs);
    
    // Call the lifecycle hook manually
    component.ngAfterViewInit();
    
    expect(component.tabGroup.ngAfterContentInit).toHaveBeenCalled();
  });

Upvotes: 1

ADITHYA D
ADITHYA D

Reputation: 11

  it('should create the component', () => {
    expect(component).toBeTruthy();
  });

  it('should set _allTabs on the MatTabGroup with mapped MatTabs from tabs QueryList', () => {
    // If using mock tabs:
    // mockTabs.push(new TabComponent()); // Add mock TabComponents
    // component.tabs = new QueryList<TabComponent>(mockTabs);

    fixture.detectChanges(); // Trigger ngAfterViewInit

    expect(tabGroup._allTabs.length).toBe(mockTabs.length); // Adjust assertion based on logic
    mockTabs.forEach((tab, index) => {
      expect(tabGroup._allTabs.get(index)).toBe(tab.matTab());
    });
  });

  it('should call ngAfterViewInit on the MatTabGroup', () => {
    const ngAfterViewInitSpy = spyOn(tabGroup, 'ngAfterViewInit');

    fixture.detectChanges(); // Trigger ngAfterViewInit

    expect(ngAfterViewInitSpy).toHaveBeenCalled();
  });

  // Add more tests as needed, considering potential edge cases and error conditions
});

Upvotes: 0

Dariusz Ostolski
Dariusz Ostolski

Reputation: 472

I think the issue you have is the same described in this github issue: https://github.com/primefaces/primeng/issues/1215

Basically the problem here is that ng-content does not provide @ContentChild when crossing component boundaries.

You can see that mat-tab uses @ContentChild: https://github.com/angular/components/blob/master/src/material/tabs/tab.ts#L56

So I think the only solution is to override it programmatically the way it is described in primeng issue.

Upvotes: 6

Phil Baird
Phil Baird

Reputation: 91

material has changed the var names, so in the solution

tabs -> _allTabs

tabGroup -> _tabBodyWrapper

Upvotes: 9

Related Questions