Skip to content

Commit a7de64a

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 0c8efa5 commit a7de64a

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, ScrollingModule],
34+
imports: [CommonModule, PortalModule, MatRippleModule, ScrollingModule, 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,
@@ -137,7 +138,9 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
137138
constructor(private _elementRef: ElementRef,
138139
private _changeDetectorRef: ChangeDetectorRef,
139140
private _viewportRuler: ViewportRuler,
140-
@Optional() private _dir: Directionality) {
141+
@Optional() private _dir: Directionality,
142+
// @breaking-change 8.0.0 `_ngZone` parameter to be made required.
143+
private _ngZone?: NgZone) {
141144
super();
142145
}
143146

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

242252
/**

0 commit comments

Comments
 (0)