Skip to content

Commit 727ce53

Browse files
crisbetommalerba
authored andcommitted
fix(tabs): crashing on chrome under certain conditions (#2411)
* fix(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. * Fix IE errors. * Add a TODO. * Revert. * Add TODO for removing the workaround.
1 parent 9e99374 commit 727ce53

File tree

3 files changed

+50
-5
lines changed

3 files changed

+50
-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: 35 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,28 @@ 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+
* TODO: This can safely be removed after we stop supporting Angular < 2.4.2. The fix landed via
151+
* https://github.com/angular/angular/commit/21030e9a1cf30e8101399d8535ed72d847a23ba6
152+
*/
153+
ngAfterContentChecked() {
154+
if (!this._canBeAnimated) {
155+
this._canBeAnimated = document.body.contains(this._elementRef.nativeElement);
156+
157+
if (this._canBeAnimated) {
158+
this._changeDetectorRef.markForCheck();
159+
}
160+
}
161+
}
162+
131163
_onTranslateTabStarted(e: AnimationTransitionEvent) {
132164
if (this._isCenterPosition(e.toState)) {
133165
this.onCentering.emit(this._elementRef.nativeElement.clientHeight);
@@ -151,7 +183,6 @@ export class MdTabBody implements OnInit {
151183
return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
152184
}
153185

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

0 commit comments

Comments
 (0)