Skip to content

Commit 4db7b41

Browse files
committed
fix(overlay): disable all animations when using the NoopAnimationsModule
Disables the CSS-based animations in the overlay when using the `NoopAnimationsModule`. Relates to #10590.
1 parent 1e1751f commit 4db7b41

File tree

5 files changed

+117
-18
lines changed

5 files changed

+117
-18
lines changed

src/cdk/overlay/_overlay.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ $backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
9090
}
9191
}
9292

93+
.cdk-overlay-backdrop-transition-disabled {
94+
// Reset the transition by overriding, rather than adding
95+
// the `transition` conditionally, so we can cover the case
96+
// where consumers specified their own transition.
97+
transition: none;
98+
}
99+
93100
.cdk-overlay-dark-backdrop {
94101
background: $cdk-overlay-dark-backdrop-background;
95102
}

src/cdk/overlay/overlay-ref.ts

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
3737
* the `_host` to its original position in the DOM when it gets re-attached.
3838
*/
3939
private _previousHostParent: HTMLElement;
40+
4041
private _keydownEventsObservable: Observable<KeyboardEvent> = Observable.create(observer => {
4142
const subscription = this._keydownEvents.subscribe(observer);
4243
this._keydownEventSubscriptions++;
@@ -47,6 +48,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
4748
};
4849
});
4950

51+
/** Whether animations are disabled for this overlay. */
52+
private readonly _animationsDisabled: boolean;
53+
5054
/** Stream of keydown events dispatched to this overlay. */
5155
_keydownEvents = new Subject<KeyboardEvent>();
5256

@@ -60,11 +64,18 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
6064
private _config: ImmutableObject<OverlayConfig>,
6165
private _ngZone: NgZone,
6266
private _keyboardDispatcher: OverlayKeyboardDispatcher,
63-
private _document: Document) {
67+
private _document: Document,
68+
/**
69+
* @deprecated `animationMode` parameter to be made required.
70+
* @breaking-change 8.0.0
71+
*/
72+
animationMode?: string) {
6473

6574
if (_config.scrollStrategy) {
6675
_config.scrollStrategy.attach(this);
6776
}
77+
78+
this._animationsDisabled = animationMode === 'NoopAnimations';
6879
}
6980

7081
/** The overlay's HTML element */
@@ -333,6 +344,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
333344
this._backdropElement = this._document.createElement('div');
334345
this._backdropElement.classList.add('cdk-overlay-backdrop');
335346

347+
if (this._animationsDisabled) {
348+
this._backdropElement.classList.add('cdk-overlay-backdrop-transition-disabled');
349+
}
350+
336351
if (this._config.backdropClass) {
337352
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
338353
}
@@ -377,24 +392,32 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
377392
detachBackdrop(): void {
378393
let backdropToDetach = this._backdropElement;
379394

380-
if (backdropToDetach) {
381-
let timeoutId: number;
382-
let finishDetach = () => {
383-
// It may not be attached to anything in certain cases (e.g. unit tests).
384-
if (backdropToDetach && backdropToDetach.parentNode) {
385-
backdropToDetach.parentNode.removeChild(backdropToDetach);
386-
}
395+
if (!backdropToDetach) {
396+
return;
397+
}
387398

388-
// It is possible that a new portal has been attached to this overlay since we started
389-
// removing the backdrop. If that is the case, only clear the backdrop reference if it
390-
// is still the same instance that we started to remove.
391-
if (this._backdropElement == backdropToDetach) {
392-
this._backdropElement = null;
393-
}
399+
let timeoutId: number;
400+
const finishDetach = () => {
401+
// It may not be attached to anything in certain cases (e.g. unit tests).
402+
if (backdropToDetach && backdropToDetach.parentNode) {
403+
backdropToDetach.parentNode.removeChild(backdropToDetach);
404+
}
394405

395-
clearTimeout(timeoutId);
396-
};
406+
// It is possible that a new portal has been attached to this overlay since we started
407+
// removing the backdrop. If that is the case, only clear the backdrop reference if it
408+
// is still the same instance that we started to remove.
409+
if (this._backdropElement == backdropToDetach) {
410+
this._backdropElement = null;
411+
}
397412

413+
clearTimeout(timeoutId);
414+
};
415+
416+
if (this._animationsDisabled) {
417+
// When the animations are disabled via the `NoopAnimationsModule`, we can be
418+
// fairly certain that they won't happen so we can remove the element immediately.
419+
finishDetach();
420+
} else {
398421
backdropToDetach.classList.remove('cdk-overlay-backdrop-showing');
399422

400423
if (this._config.backdropClass) {

src/cdk/overlay/overlay.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
PositionStrategy,
2727
ScrollStrategy,
2828
} from './index';
29+
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
2930

3031

3132
describe('Overlay', () => {
@@ -598,6 +599,64 @@ describe('Overlay', () => {
598599
expect(backdrop.classList).toContain('cdk-overlay-transparent-backdrop');
599600
});
600601

602+
it('should not disable the backdrop transition if animations are enabled', () => {
603+
overlayContainer.ngOnDestroy();
604+
605+
TestBed
606+
.resetTestingModule()
607+
.configureTestingModule({
608+
imports: [OverlayModule, PortalModule, OverlayTestModule, BrowserAnimationsModule],
609+
})
610+
.compileComponents();
611+
612+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
613+
overlay = o;
614+
overlayContainer = oc;
615+
overlayContainerElement = oc.getContainerElement();
616+
})();
617+
618+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
619+
fixture.detectChanges();
620+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
621+
viewContainerFixture = fixture;
622+
623+
let overlayRef = overlay.create(config);
624+
overlayRef.attach(componentPortal);
625+
viewContainerFixture.detectChanges();
626+
627+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
628+
expect(backdrop.classList).not.toContain('cdk-overlay-backdrop-transition-disabled');
629+
});
630+
631+
it('should disable the backdrop transition when animations are disabled', () => {
632+
overlayContainer.ngOnDestroy();
633+
634+
TestBed
635+
.resetTestingModule()
636+
.configureTestingModule({
637+
imports: [OverlayModule, PortalModule, OverlayTestModule, NoopAnimationsModule],
638+
})
639+
.compileComponents();
640+
641+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
642+
overlay = o;
643+
overlayContainer = oc;
644+
overlayContainerElement = oc.getContainerElement();
645+
})();
646+
647+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
648+
fixture.detectChanges();
649+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
650+
viewContainerFixture = fixture;
651+
652+
let overlayRef = overlay.create(config);
653+
overlayRef.attach(componentPortal);
654+
viewContainerFixture.detectChanges();
655+
656+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
657+
expect(backdrop.classList).toContain('cdk-overlay-backdrop-transition-disabled');
658+
});
659+
601660
it('should apply multiple custom classes to the overlay', () => {
602661
config.backdropClass = ['custom-class-1', 'custom-class-2'];
603662

src/cdk/overlay/overlay.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import {
1616
Injectable,
1717
Injector,
1818
NgZone,
19+
Optional,
1920
} from '@angular/core';
2021
import {OverlayKeyboardDispatcher} from './keyboard/overlay-keyboard-dispatcher';
2122
import {OverlayConfig} from './overlay-config';
2223
import {OverlayContainer} from './overlay-container';
2324
import {OverlayRef} from './overlay-ref';
2425
import {OverlayPositionBuilder} from './position/overlay-position-builder';
2526
import {ScrollStrategyOptions} from './scroll/index';
27+
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
2628

2729

2830
/** Next overlay unique ID. */
@@ -53,7 +55,12 @@ export class Overlay {
5355
private _injector: Injector,
5456
private _ngZone: NgZone,
5557
@Inject(DOCUMENT) private _document: any,
56-
private _directionality: Directionality) { }
58+
private _directionality: Directionality,
59+
/**
60+
* @deprecated `animationMode` parameter to be made required.
61+
* @deletion-target 8.0.0
62+
*/
63+
@Optional() @Inject(ANIMATION_MODULE_TYPE) private _animationMode?: string) { }
5764

5865
/**
5966
* Creates an overlay.
@@ -69,7 +76,7 @@ export class Overlay {
6976
overlayConfig.direction = overlayConfig.direction || this._directionality.value;
7077

7178
return new OverlayRef(portalOutlet, host, pane, overlayConfig, this._ngZone,
72-
this._keyboardDispatcher, this._document);
79+
this._keyboardDispatcher, this._document, this._animationMode);
7380
}
7481

7582
/**

src/lib/datepicker/datepicker.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,13 +356,16 @@ describe('MatDatepicker', () => {
356356
for (let i = 0; i < 3; i++) {
357357
testComponent.datepicker.open();
358358
fixture.detectChanges();
359+
flush();
359360

360361
testComponent.datepicker.close();
361362
fixture.detectChanges();
363+
flush();
362364
}
363365

364366
testComponent.datepicker.open();
365367
fixture.detectChanges();
368+
flush();
366369

367370
const spy = jasmine.createSpy('close event spy');
368371
const subscription = testComponent.datepicker.closedStream.subscribe(spy);

0 commit comments

Comments
 (0)