Skip to content

Commit 7ec6803

Browse files
crisbetojelbourn
authored andcommitted
fix(tabs): pagination state not updated when tab content changes (#12911)
Fixes the displayed state of the tabs pagination not being updated when the content of the tabs changes. The issue comes from the fact that the content change callback runs outside the `NgZone`. Fixes #12901.
1 parent d6ff97a commit 7ec6803

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed

src/lib/tabs/tab-header.spec.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {MatInkBar} from './ink-bar';
1919
import {MatTabHeader} from './tab-header';
2020
import {MatTabLabelWrapper} from './tab-label-wrapper';
2121
import {Subject} from 'rxjs';
22+
import {ObserversModule, MutationObserverFactory} from '@angular/cdk/observers';
2223

2324

2425
describe('MatTabHeader', () => {
@@ -30,7 +31,7 @@ describe('MatTabHeader', () => {
3031
beforeEach(async(() => {
3132
dir = 'ltr';
3233
TestBed.configureTestingModule({
33-
imports: [CommonModule, PortalModule, MatRippleModule, ScrollDispatchModule],
34+
imports: [CommonModule, PortalModule, MatRippleModule, ScrollDispatchModule, ObserversModule],
3435
declarations: [
3536
MatTabHeader,
3637
MatInkBar,
@@ -345,6 +346,41 @@ describe('MatTabHeader', () => {
345346
discardPeriodicTasks();
346347
}));
347348

349+
it('should update the pagination state if the content of the labels changes', () => {
350+
const mutationCallbacks: Function[] = [];
351+
TestBed.overrideProvider(MutationObserverFactory, {
352+
useValue: {
353+
// Stub out the MutationObserver since the native one is async.
354+
create: function(callback: Function) {
355+
mutationCallbacks.push(callback);
356+
return {observe: () => {}, disconnect: () => {}};
357+
}
358+
}
359+
});
360+
361+
fixture = TestBed.createComponent(SimpleTabHeaderApp);
362+
fixture.detectChanges();
363+
364+
const tabHeaderElement: HTMLElement =
365+
fixture.nativeElement.querySelector('.mat-tab-header');
366+
const labels =
367+
Array.from<HTMLElement>(fixture.nativeElement.querySelectorAll('.label-content'));
368+
const extraText = new Array(100).fill('w').join();
369+
const enabledClass = 'mat-tab-header-pagination-controls-enabled';
370+
371+
expect(tabHeaderElement.classList).not.toContain(enabledClass);
372+
373+
labels.forEach(label => {
374+
label.style.width = '';
375+
label.textContent += extraText;
376+
});
377+
378+
mutationCallbacks.forEach(callback => callback());
379+
fixture.detectChanges();
380+
381+
expect(tabHeaderElement.classList).toContain(enabledClass);
382+
});
383+
348384
});
349385
});
350386

@@ -359,7 +395,7 @@ interface Tab {
359395
<mat-tab-header [selectedIndex]="selectedIndex" [disableRipple]="disableRipple"
360396
(indexFocused)="focusedIndex = $event"
361397
(selectFocusedIndex)="selectedIndex = $event">
362-
<div matTabLabelWrapper style="min-width: 30px; width: 30px"
398+
<div matTabLabelWrapper class="label-content" style="min-width: 30px; width: 30px"
363399
*ngFor="let tab of tabs; let i = index"
364400
[disabled]="!!tab.disabled"
365401
(click)="selectedIndex = i">

src/lib/tabs/tab-header.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
ElementRef,
2121
EventEmitter,
2222
Input,
23+
NgZone,
2324
OnDestroy,
2425
Optional,
2526
Output,
@@ -136,7 +137,9 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
136137
constructor(private _elementRef: ElementRef,
137138
private _changeDetectorRef: ChangeDetectorRef,
138139
private _viewportRuler: ViewportRuler,
139-
@Optional() private _dir: Directionality) {
140+
@Optional() private _dir: Directionality,
141+
// @breaking-change 8.0.0 `_ngZone` parameter to be made required.
142+
private _ngZone?: NgZone) {
140143
super();
141144
}
142145

@@ -233,9 +236,16 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
233236
* Callback for when the MutationObserver detects that the content has changed.
234237
*/
235238
_onContentChanges() {
236-
this._updatePagination();
237-
this._alignInkBarToSelectedTab();
238-
this._changeDetectorRef.markForCheck();
239+
const zoneCallback = () => {
240+
this._updatePagination();
241+
this._alignInkBarToSelectedTab();
242+
this._changeDetectorRef.markForCheck();
243+
};
244+
245+
// The content observer runs outside the `NgZone` by default, which
246+
// means that we need to bring the callback back in ourselves.
247+
// @breaking-change 8.0.0 Remove null check for `_ngZone` once it's a required parameter.
248+
this._ngZone ? this._ngZone.run(zoneCallback) : zoneCallback();
239249
}
240250

241251
/**

0 commit comments

Comments
 (0)