From 938dcca30cd43ea21f219d5582e96fc5aab5f969 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 26 Dec 2016 16:52:10 +0200 Subject: [PATCH 1/5] 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. --- src/lib/tabs/tab-body.html | 2 +- src/lib/tabs/tab-body.spec.ts | 14 ++++++++++++++ src/lib/tabs/tab-body.ts | 36 +++++++++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 5 deletions(-) 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..2b73e63622ab 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,25 @@ 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. + */ + ngAfterContentChecked() { + if (!this._canBeAnimated) { + this._canBeAnimated = document.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 +180,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' || From 71ec389cb62bb9839d5003cf38e29a9a90b29530 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 26 Dec 2016 20:05:22 +0200 Subject: [PATCH 2/5] Fix IE errors. --- src/lib/tabs/tab-body.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index 2b73e63622ab..352069442136 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -149,7 +149,7 @@ export class MdTabBody implements OnInit, AfterViewChecked, AfterContentChecked */ ngAfterContentChecked() { if (!this._canBeAnimated) { - this._canBeAnimated = document.contains(this._elementRef.nativeElement); + this._canBeAnimated = document.body.contains(this._elementRef.nativeElement); if (this._canBeAnimated) { this._changeDetectorRef.markForCheck(); From b3fc47ad06180cd5f6c6391e64ff5a7eca2119f3 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Tue, 27 Dec 2016 22:12:45 +0200 Subject: [PATCH 3/5] Add a TODO. --- src/lib/tabs/tab-body.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index 352069442136..45812dc20e07 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -146,6 +146,8 @@ export class MdTabBody implements OnInit, AfterViewChecked, AfterContentChecked * 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(crisbeto): This workaround can be removed safely once Angular < 2.4.1 isn't supported. */ ngAfterContentChecked() { if (!this._canBeAnimated) { From 6e30649875bc6059721035ca9badd36284eb3b56 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Tue, 27 Dec 2016 22:30:58 +0200 Subject: [PATCH 4/5] Revert. --- src/lib/tabs/tab-body.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index 45812dc20e07..352069442136 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -146,8 +146,6 @@ export class MdTabBody implements OnInit, AfterViewChecked, AfterContentChecked * 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(crisbeto): This workaround can be removed safely once Angular < 2.4.1 isn't supported. */ ngAfterContentChecked() { if (!this._canBeAnimated) { From f8dc030768695d72107799f82791a6f946b6f192 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Tue, 10 Jan 2017 21:50:31 +0100 Subject: [PATCH 5/5] Add TODO for removing the workaround. --- src/lib/tabs/tab-body.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index 352069442136..1de1100616e5 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -146,6 +146,9 @@ export class MdTabBody implements OnInit, AfterViewChecked, AfterContentChecked * 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) {