Skip to content

Commit

Permalink
fix(material/tabs): Eliminate ExpressionChanged... errors for active …
Browse files Browse the repository at this point in the history
…tab changes

Using signals eliminates the `ExpressionChangedAfterItHasBeenCheckedError` when
an active tab changes after the tab panel has been checked.

fixes angular#28379
  • Loading branch information
atscott committed Jan 5, 2024
1 parent 4bae885 commit bf7b002
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 5 deletions.
32 changes: 30 additions & 2 deletions src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts
@@ -1,6 +1,6 @@
import {ENTER, SPACE} from '@angular/cdk/keycodes';
import {waitForAsync, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {Component, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {Component, Input, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core';
import {By} from '@angular/platform-browser';
import {
Expand All @@ -11,7 +11,7 @@ import {
import {Direction, Directionality} from '@angular/cdk/bidi';
import {Subject} from 'rxjs';
import {MatTabsModule} from '../module';
import {MatTabLink, MatTabNav} from './tab-nav-bar';
import {MatTabLink, MatTabNav, MatTabNavPanel} from './tab-nav-bar';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MAT_TABS_CONFIG} from '../index';

Expand Down Expand Up @@ -493,6 +493,34 @@ describe('MatTabNavBar with a default config', () => {
expect(indicatorElement.parentElement).toBe(contentElement);
});
});
it('allows tab panel to be in component above nav bar', () => {
@Component({
standalone: true,
selector: 'nav-wrapper',
imports: [MatTabsModule],
template: `
<nav mat-tab-nav-bar [tabPanel]="tabPanel">
<a mat-tab-link [active]="true"> link </a>
</nav>
`,
})
class NavWrapper {
@Input() tabPanel!: MatTabNavPanel;
}

@Component({
standalone: true,
imports: [MatTabsModule, NavWrapper],
template: `
<mat-tab-nav-panel #tabPanel />
<nav-wrapper [tabPanel]="tabPanel" />
`,
})
class TabPanelCmp {}

const comp = TestBed.createComponent(TabPanelCmp);
expect(() => comp.detectChanges()).not.toThrow();
});

describe('MatTabNavBar with enabled animations', () => {
beforeEach(fakeAsync(() => {
Expand Down
7 changes: 4 additions & 3 deletions src/material/tabs/tab-nav-bar/tab-nav-bar.ts
Expand Up @@ -24,6 +24,7 @@ import {
OnDestroy,
Optional,
QueryList,
signal,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
Expand Down Expand Up @@ -211,7 +212,7 @@ export class MatTabNav
this._changeDetectorRef.markForCheck();

if (this.tabPanel) {
this.tabPanel._activeTabId = items[i].id;
this.tabPanel._activeTabId.set(items[i].id);
}

return;
Expand Down Expand Up @@ -416,7 +417,7 @@ export class MatTabLink
exportAs: 'matTabNavPanel',
template: '<ng-content></ng-content>',
host: {
'[attr.aria-labelledby]': '_activeTabId',
'[attr.aria-labelledby]': '_activeTabId()',
'[attr.id]': 'id',
'class': 'mat-mdc-tab-nav-panel',
'role': 'tabpanel',
Expand All @@ -430,5 +431,5 @@ export class MatTabNavPanel {
@Input() id = `mat-tab-nav-panel-${nextUniqueId++}`;

/** Id of the active tab in the nav bar. */
_activeTabId?: string;
_activeTabId = signal<string | undefined>(undefined);
}

0 comments on commit bf7b002

Please sign in to comment.