diff --git a/src/lib/tabs/tab-body.html b/src/lib/tabs/tab-body.html index 211fc4efe247..ff8cf9fcbad7 100644 --- a/src/lib/tabs/tab-body.html +++ b/src/lib/tabs/tab-body.html @@ -1,5 +1,5 @@
diff --git a/src/lib/tabs/tab-body.spec.ts b/src/lib/tabs/tab-body.spec.ts index 0dbae8ca96ac..f0bae7c6a757 100644 --- a/src/lib/tabs/tab-body.spec.ts +++ b/src/lib/tabs/tab-body.spec.ts @@ -168,6 +168,20 @@ describe('MdTabBody', () => { expect(fixture.componentInstance.mdTabBody._portalHost.hasAttached()).toBe(false); })); }); + + it('it should toggle the canBeAnimated flag', () => { + let fixture: ComponentFixture; + let tabBody: MdTabBody; + + fixture = TestBed.createComponent(SimpleTabBodyApp); + tabBody = fixture.componentInstance.mdTabBody; + + expect(tabBody._canBeAnimated).toBe(false); + + fixture.detectChanges(); + + expect(tabBody._canBeAnimated).toBe(true); + }); }); diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index 335158fe8879..1de1100616e5 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -12,7 +12,10 @@ import { transition, AnimationTransitionEvent, ElementRef, - Optional + Optional, + ChangeDetectorRef, + AfterViewChecked, + AfterContentChecked, } from '@angular/core'; import {TemplatePortal, PortalHostDirective, Dir, LayoutDirection} from '../core'; import 'rxjs/add/operator/map'; @@ -65,7 +68,7 @@ export type MdTabBodyOriginState = 'left' | 'right'; ]) ] }) -export class MdTabBody implements OnInit { +export class MdTabBody implements OnInit, AfterViewChecked, AfterContentChecked { /** The portal host inside of this container into which the tab body content will be loaded. */ @ViewChild(PortalHostDirective) _portalHost: PortalHostDirective; @@ -92,6 +95,10 @@ export class MdTabBody implements OnInit { } } + /** Whether the element is allowed to be animated. */ + _canBeAnimated: boolean = false; + + /** The origin position from which this tab should appear when it is centered into view. */ _origin: MdTabBodyOriginState; /** The origin position from which this tab should appear when it is centered into view. */ @@ -106,7 +113,10 @@ export class MdTabBody implements OnInit { } } - constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {} + constructor( + @Optional() private _dir: Dir, + private _elementRef: ElementRef, + private _changeDetectorRef: ChangeDetectorRef) { } /** * 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 { } } + /** + * After the content has been checked, determines whether the element should be allowed to + * animate. This has to be limited, because under a specific set of circumstances (see #2151), + * the animations can be triggered too early, which either crashes Chrome by putting it into an + * infinite loop (with Angular < 2.3.0) or throws an error because the element doesn't have a + * computed style (with Angular > 2.3.0). This can alternatively be determined by checking the + * transform: canBeAnimated = getComputedStyle(element) !== '', however document.contains should + * be faster since it doesn't cause a reflow. + * + * TODO: This can safely be removed after we stop supporting Angular < 2.4.2. The fix landed via + * https://github.com/angular/angular/commit/21030e9a1cf30e8101399d8535ed72d847a23ba6 + */ + ngAfterContentChecked() { + if (!this._canBeAnimated) { + this._canBeAnimated = document.body.contains(this._elementRef.nativeElement); + + if (this._canBeAnimated) { + this._changeDetectorRef.markForCheck(); + } + } + } + _onTranslateTabStarted(e: AnimationTransitionEvent) { if (this._isCenterPosition(e.toState)) { this.onCentering.emit(this._elementRef.nativeElement.clientHeight); @@ -151,7 +183,6 @@ export class MdTabBody implements OnInit { return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; } - /** Whether the provided position state is considered center, regardless of origin. */ private _isCenterPosition(position: MdTabBodyPositionState|string): boolean { return position == 'center' ||