From a7de64a4cdadc7c1b208e308820307438983790c Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 12 Sep 2018 00:26:24 +0200 Subject: [PATCH] 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. --- src/lib/tabs/tab-header.spec.ts | 40 +++++++++++++++++++++++++++++++-- src/lib/tabs/tab-header.ts | 18 +++++++++++---- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/lib/tabs/tab-header.spec.ts b/src/lib/tabs/tab-header.spec.ts index 3ff61a32310..b5aaa2a0848 100644 --- a/src/lib/tabs/tab-header.spec.ts +++ b/src/lib/tabs/tab-header.spec.ts @@ -19,6 +19,7 @@ import {MatInkBar} from './ink-bar'; import {MatTabHeader} from './tab-header'; import {MatTabLabelWrapper} from './tab-label-wrapper'; import {Subject} from 'rxjs'; +import {ObserversModule, MutationObserverFactory} from '@angular/cdk/observers'; describe('MatTabHeader', () => { @@ -30,7 +31,7 @@ describe('MatTabHeader', () => { beforeEach(async(() => { dir = 'ltr'; TestBed.configureTestingModule({ - imports: [CommonModule, PortalModule, MatRippleModule, ScrollingModule], + imports: [CommonModule, PortalModule, MatRippleModule, ScrollingModule, ObserversModule], declarations: [ MatTabHeader, MatInkBar, @@ -345,6 +346,41 @@ describe('MatTabHeader', () => { discardPeriodicTasks(); })); + it('should update the pagination state if the content of the labels changes', () => { + const mutationCallbacks: Function[] = []; + TestBed.overrideProvider(MutationObserverFactory, { + useValue: { + // Stub out the MutationObserver since the native one is async. + create: function(callback: Function) { + mutationCallbacks.push(callback); + return {observe: () => {}, disconnect: () => {}}; + } + } + }); + + fixture = TestBed.createComponent(SimpleTabHeaderApp); + fixture.detectChanges(); + + const tabHeaderElement: HTMLElement = + fixture.nativeElement.querySelector('.mat-tab-header'); + const labels = + Array.from(fixture.nativeElement.querySelectorAll('.label-content')); + const extraText = new Array(100).fill('w').join(); + const enabledClass = 'mat-tab-header-pagination-controls-enabled'; + + expect(tabHeaderElement.classList).not.toContain(enabledClass); + + labels.forEach(label => { + label.style.width = ''; + label.textContent += extraText; + }); + + mutationCallbacks.forEach(callback => callback()); + fixture.detectChanges(); + + expect(tabHeaderElement.classList).toContain(enabledClass); + }); + }); }); @@ -359,7 +395,7 @@ interface Tab { -
diff --git a/src/lib/tabs/tab-header.ts b/src/lib/tabs/tab-header.ts index cd930500c6a..058bc62e525 100644 --- a/src/lib/tabs/tab-header.ts +++ b/src/lib/tabs/tab-header.ts @@ -20,6 +20,7 @@ import { ElementRef, EventEmitter, Input, + NgZone, OnDestroy, Optional, Output, @@ -137,7 +138,9 @@ export class MatTabHeader extends _MatTabHeaderMixinBase constructor(private _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, private _viewportRuler: ViewportRuler, - @Optional() private _dir: Directionality) { + @Optional() private _dir: Directionality, + // @breaking-change 8.0.0 `_ngZone` parameter to be made required. + private _ngZone?: NgZone) { super(); } @@ -234,9 +237,16 @@ export class MatTabHeader extends _MatTabHeaderMixinBase * Callback for when the MutationObserver detects that the content has changed. */ _onContentChanges() { - this._updatePagination(); - this._alignInkBarToSelectedTab(); - this._changeDetectorRef.markForCheck(); + const zoneCallback = () => { + this._updatePagination(); + this._alignInkBarToSelectedTab(); + this._changeDetectorRef.markForCheck(); + }; + + // The content observer runs outside the `NgZone` by default, which + // means that we need to bring the callback back in ourselves. + // @breaking-change 8.0.0 Remove null check for `_ngZone` once it's a required parameter. + this._ngZone ? this._ngZone.run(zoneCallback) : zoneCallback(); } /**