Skip to content

Commit

Permalink
fix(tabs): avoid hitting change detection if text content hasn't chan…
Browse files Browse the repository at this point in the history
…ged (#14251)

Currently we trigger a change detection run when the content of the tab header changes. Since the `MutationObserver` callback can fire if the text node got swapped out, but the actual text didn't change, we could get into an infinite change detection loop if a poorly constructed data binding is used. These changes add a check that will only do extra work if the content has changed.

Fixes #14249.
  • Loading branch information
crisbeto authored and jelbourn committed Dec 3, 2018
1 parent 63174d2 commit 9778af2
Showing 1 changed file with 22 additions and 11 deletions.
33 changes: 22 additions & 11 deletions src/lib/tabs/tab-header.ts
Expand Up @@ -114,7 +114,8 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
/** Used to manage focus between the tabs. */
private _keyManager: FocusKeyManager<MatTabLabelWrapper>;

private _selectedIndex: number = 0;
/** Cached text content of the header. */
private _currentTextContent: string;

/** The index of the active tab. */
@Input()
Expand All @@ -128,6 +129,7 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
this._keyManager.updateActiveItemIndex(value);
}
}
private _selectedIndex: number = 0;

/** Event emitted when the option is selected. */
@Output() readonly selectFocusedIndex = new EventEmitter();
Expand Down Expand Up @@ -237,16 +239,25 @@ export class MatTabHeader extends _MatTabHeaderMixinBase
* Callback for when the MutationObserver detects that the content has changed.
*/
_onContentChanges() {
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();
const textContent = this._elementRef.nativeElement.textContent;

// We need to diff the text content of the header, because the MutationObserver callback
// will fire even if the text content didn't change which is inefficient and is prone
// to infinite loops if a poorly constructed expression is passed in (see #14249).
if (textContent !== this._currentTextContent) {
this._currentTextContent = textContent;

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();
}
}

/**
Expand Down

0 comments on commit 9778af2

Please sign in to comment.