diff --git a/src/material/tabs/tab-group.spec.ts b/src/material/tabs/tab-group.spec.ts
index 71705913bbfa..bfc7c9fd86e1 100644
--- a/src/material/tabs/tab-group.spec.ts
+++ b/src/material/tabs/tab-group.spec.ts
@@ -898,6 +898,29 @@ describe('MatTabGroup', () => {
'Child 3',
]);
});
+
+ it('should emit selectedTabChange in the same task as selectedIndexChange', async () => {
+ const fixture = TestBed.createComponent(TabGroupWithBoundIndex);
+ fixture.detectChanges();
+
+ const tabGroup = fixture.componentInstance.tabGroup;
+ const emissions: string[] = [];
+
+ tabGroup.selectedIndexChange.subscribe(() => emissions.push('selectedIndexChange'));
+ tabGroup.selectedTabChange.subscribe(() => emissions.push('selectedTabChange'));
+
+ fixture.componentInstance.selectedIndex = 2;
+ fixture.changeDetectorRef.markForCheck();
+ fixture.detectChanges();
+
+ // Flush the microtask queue. Both events should have been delivered by now. If
+ // `selectedTabChange` is delivered in a later task, an index that has since been
+ // superseded can be handed to the app, which can push a stale value back into
+ // `selectedIndex` and cause an endless toggle.
+ await Promise.resolve();
+
+ expect(emissions).toEqual(['selectedIndexChange', 'selectedTabChange']);
+ });
});
describe('nested tabs', () => {
@@ -1649,3 +1672,19 @@ class TabsWithAlignConfig {}
changeDetection: ChangeDetectionStrategy.Eager,
})
class TabsWithAlignCenter {}
+
+@Component({
+ template: `
+
+ One
+ Two
+ Three
+
+ `,
+ imports: [MatTabsModule],
+ changeDetection: ChangeDetectionStrategy.Eager,
+})
+class TabGroupWithBoundIndex {
+ @ViewChild(MatTabGroup) tabGroup!: MatTabGroup;
+ selectedIndex = 0;
+}
diff --git a/src/material/tabs/tab-group.ts b/src/material/tabs/tab-group.ts
index e5cc623effc7..1389369f15af 100644
--- a/src/material/tabs/tab-group.ts
+++ b/src/material/tabs/tab-group.ts
@@ -282,7 +282,7 @@ export class MatTabGroup
/** Event emitted when the tab selection has changed. */
@Output() readonly selectedTabChange: EventEmitter =
- new EventEmitter(true);
+ new EventEmitter();
private _groupId: string;
@@ -332,7 +332,6 @@ export class MatTabGroup
const isFirstRun = this._selectedIndex == null;
if (!isFirstRun) {
- this.selectedTabChange.emit(this._createChangeEvent(indexToSelect));
// Preserve the height so page doesn't scroll up during tab change.
// Fixes https://stackblitz.com/edit/mat-tabs-scroll-page-top-on-tab-change
const wrapper = this._tabBodyWrapper.nativeElement;
@@ -346,6 +345,7 @@ export class MatTabGroup
if (!isFirstRun) {
this.selectedIndexChange.emit(indexToSelect);
+ this.selectedTabChange.emit(this._createChangeEvent(indexToSelect));
// Clear the min-height, this was needed during tab change to avoid
// unnecessary scrolling.
this._tabBodyWrapper.nativeElement.style.minHeight = '';