Skip to content

Commit 8732369

Browse files
committed
fix(tabs): tabs crashing on chrome under certain conditions
Prevents the tabs from either crashing Chrome (in Angular < 2.3) or throwing an animation error (in Angular >= 2.3). This was happening when the animations get triggered before the element is inserted into the DOM and thus doesn't have a computed `transform` value. Fixes #2151.
1 parent 026c70a commit 8732369

File tree

3 files changed

+47
-5
lines changed

3 files changed

+47
-5
lines changed

src/lib/tabs/tab-body.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="md-tab-body-content" #content
2-
[@translateTab]="_position"
2+
[@translateTab]="_canBeAnimated ? _position : null"
33
(@translateTab.start)="_onTranslateTabStarted($event)"
44
(@translateTab.done)="_onTranslateTabComplete($event)">
55
<template cdkPortalHost></template>

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,20 @@ describe('MdTabBody', () => {
168168
expect(fixture.componentInstance.mdTabBody._portalHost.hasAttached()).toBe(false);
169169
}));
170170
});
171+
172+
it('it should toggle the canBeAnimated flag', () => {
173+
let fixture: ComponentFixture<SimpleTabBodyApp>;
174+
let tabBody: MdTabBody;
175+
176+
fixture = TestBed.createComponent(SimpleTabBodyApp);
177+
tabBody = fixture.componentInstance.mdTabBody;
178+
179+
expect(tabBody._canBeAnimated).toBe(false);
180+
181+
fixture.detectChanges();
182+
183+
expect(tabBody._canBeAnimated).toBe(true);
184+
});
171185
});
172186

173187

src/lib/tabs/tab-body.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
transition,
1313
AnimationTransitionEvent,
1414
ElementRef,
15-
Optional
15+
Optional,
16+
ChangeDetectorRef,
17+
AfterViewChecked,
18+
AfterContentChecked,
1619
} from '@angular/core';
1720
import {TemplatePortal, PortalHostDirective, Dir, LayoutDirection} from '../core';
1821
import 'rxjs/add/operator/map';
@@ -65,7 +68,7 @@ export type MdTabBodyOriginState = 'left' | 'right';
6568
])
6669
]
6770
})
68-
export class MdTabBody implements OnInit {
71+
export class MdTabBody implements OnInit, AfterViewChecked, AfterContentChecked {
6972
/** The portal host inside of this container into which the tab body content will be loaded. */
7073
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
7174

@@ -92,6 +95,10 @@ export class MdTabBody implements OnInit {
9295
}
9396
}
9497

98+
/** Whether the element is allowed to be animated. */
99+
_canBeAnimated: boolean = false;
100+
101+
/** The origin position from which this tab should appear when it is centered into view. */
95102
_origin: MdTabBodyOriginState;
96103

97104
/** The origin position from which this tab should appear when it is centered into view. */
@@ -106,7 +113,10 @@ export class MdTabBody implements OnInit {
106113
}
107114
}
108115

109-
constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {}
116+
constructor(
117+
@Optional() private _dir: Dir,
118+
private _elementRef: ElementRef,
119+
private _changeDetectorRef: ChangeDetectorRef) { }
110120

111121
/**
112122
* After initialized, check if the content is centered and has an origin. If so, set the
@@ -128,6 +138,25 @@ export class MdTabBody implements OnInit {
128138
}
129139
}
130140

141+
/**
142+
* After the content has been checked, determines whether the element should be allowed to
143+
* animate. This has to be limited, because under a specific set of circumstances (see #2151),
144+
* the animations can be triggered too early, which either crashes Chrome by putting it into an
145+
* infinite loop (with Angular < 2.3.0) or throws an error because the element doesn't have a
146+
* computed style (with Angular > 2.3.0). This can alternatively be determined by checking the
147+
* transform: canBeAnimated = getComputedStyle(element) !== '', however document.contains should
148+
* be faster since it doesn't cause a reflow.
149+
*/
150+
ngAfterContentChecked() {
151+
if (!this._canBeAnimated) {
152+
this._canBeAnimated = document.contains(this._elementRef.nativeElement);
153+
154+
if (this._canBeAnimated) {
155+
this._changeDetectorRef.markForCheck();
156+
}
157+
}
158+
}
159+
131160
_onTranslateTabStarted(e: AnimationTransitionEvent) {
132161
if (this._isCenterPosition(e.toState)) {
133162
this.onCentering.emit(this._elementRef.nativeElement.clientHeight);
@@ -151,7 +180,6 @@ export class MdTabBody implements OnInit {
151180
return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
152181
}
153182

154-
155183
/** Whether the provided position state is considered center, regardless of origin. */
156184
private _isCenterPosition(position: MdTabBodyPositionState|string): boolean {
157185
return position == 'center' ||

0 commit comments

Comments
 (0)