Skip to content

Commit b3750f4

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 4166d16 commit b3750f4

File tree

5 files changed

+116
-18
lines changed

5 files changed

+116
-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: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
3232
private _attachments = new Subject<void>();
3333
private _detachments = new Subject<void>();
3434

35+
/** Whether animations are disabled for this overlay. */
36+
private readonly _animationsDisabled: boolean;
37+
3538
/** Stream of keydown events dispatched to this overlay. */
3639
_keydownEvents = new Subject<KeyboardEvent>();
3740

@@ -42,11 +45,18 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
4245
private _config: ImmutableObject<OverlayConfig>,
4346
private _ngZone: NgZone,
4447
private _keyboardDispatcher: OverlayKeyboardDispatcher,
45-
private _document: Document) {
48+
private _document: Document,
49+
/**
50+
* @deprecated `animationMode` parameter to be made required.
51+
* @deletion-target 8.0.0
52+
*/
53+
animationMode?: string) {
4654

4755
if (_config.scrollStrategy) {
4856
_config.scrollStrategy.attach(this);
4957
}
58+
59+
this._animationsDisabled = animationMode === 'NoopAnimations';
5060
}
5161

5262
/** The overlay's HTML element */
@@ -287,6 +297,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
287297
this._backdropElement = this._document.createElement('div');
288298
this._backdropElement.classList.add('cdk-overlay-backdrop');
289299

300+
if (this._animationsDisabled) {
301+
this._backdropElement.classList.add('cdk-overlay-backdrop-transition-disabled');
302+
}
303+
290304
if (this._config.backdropClass) {
291305
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
292306
}
@@ -331,24 +345,32 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
331345
detachBackdrop(): void {
332346
let backdropToDetach = this._backdropElement;
333347

334-
if (backdropToDetach) {
335-
let timeoutId: number;
336-
let finishDetach = () => {
337-
// It may not be attached to anything in certain cases (e.g. unit tests).
338-
if (backdropToDetach && backdropToDetach.parentNode) {
339-
backdropToDetach.parentNode.removeChild(backdropToDetach);
340-
}
348+
if (!backdropToDetach) {
349+
return;
350+
}
341351

342-
// It is possible that a new portal has been attached to this overlay since we started
343-
// removing the backdrop. If that is the case, only clear the backdrop reference if it
344-
// is still the same instance that we started to remove.
345-
if (this._backdropElement == backdropToDetach) {
346-
this._backdropElement = null;
347-
}
352+
let timeoutId: number;
353+
const finishDetach = () => {
354+
// It may not be attached to anything in certain cases (e.g. unit tests).
355+
if (backdropToDetach && backdropToDetach.parentNode) {
356+
backdropToDetach.parentNode.removeChild(backdropToDetach);
357+
}
348358

349-
clearTimeout(timeoutId);
350-
};
359+
// It is possible that a new portal has been attached to this overlay since we started
360+
// removing the backdrop. If that is the case, only clear the backdrop reference if it
361+
// is still the same instance that we started to remove.
362+
if (this._backdropElement == backdropToDetach) {
363+
this._backdropElement = null;
364+
}
365+
366+
clearTimeout(timeoutId);
367+
};
351368

369+
if (this._animationsDisabled) {
370+
// When the animations are disabled via the `NoopAnimationsModule`, we can be
371+
// fairly certain that they won't happen so we can remove the element immediately.
372+
finishDetach();
373+
} else {
352374
backdropToDetach.classList.remove('cdk-overlay-backdrop-showing');
353375

354376
if (this._config.backdropClass) {

src/cdk/overlay/overlay.spec.ts

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

2930

3031
describe('Overlay', () => {
@@ -562,6 +563,64 @@ describe('Overlay', () => {
562563
expect(backdrop.classList).toContain('cdk-overlay-transparent-backdrop');
563564
});
564565

566+
it('should not disable the backdrop transition if animations are enabled', () => {
567+
overlayContainer.ngOnDestroy();
568+
569+
TestBed
570+
.resetTestingModule()
571+
.configureTestingModule({
572+
imports: [OverlayModule, PortalModule, OverlayTestModule, BrowserAnimationsModule],
573+
})
574+
.compileComponents();
575+
576+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
577+
overlay = o;
578+
overlayContainer = oc;
579+
overlayContainerElement = oc.getContainerElement();
580+
})();
581+
582+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
583+
fixture.detectChanges();
584+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
585+
viewContainerFixture = fixture;
586+
587+
let overlayRef = overlay.create(config);
588+
overlayRef.attach(componentPortal);
589+
viewContainerFixture.detectChanges();
590+
591+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
592+
expect(backdrop.classList).not.toContain('cdk-overlay-backdrop-transition-disabled');
593+
});
594+
595+
it('should disable the backdrop transition when animations are disabled', () => {
596+
overlayContainer.ngOnDestroy();
597+
598+
TestBed
599+
.resetTestingModule()
600+
.configureTestingModule({
601+
imports: [OverlayModule, PortalModule, OverlayTestModule, NoopAnimationsModule],
602+
})
603+
.compileComponents();
604+
605+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
606+
overlay = o;
607+
overlayContainer = oc;
608+
overlayContainerElement = oc.getContainerElement();
609+
})();
610+
611+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
612+
fixture.detectChanges();
613+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
614+
viewContainerFixture = fixture;
615+
616+
let overlayRef = overlay.create(config);
617+
overlayRef.attach(componentPortal);
618+
viewContainerFixture.detectChanges();
619+
620+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
621+
expect(backdrop.classList).toContain('cdk-overlay-backdrop-transition-disabled');
622+
});
623+
565624
it('should apply multiple custom classes to the overlay', () => {
566625
config.backdropClass = ['custom-class-1', 'custom-class-2'];
567626

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
@@ -346,13 +346,16 @@ describe('MatDatepicker', () => {
346346
for (let i = 0; i < 3; i++) {
347347
testComponent.datepicker.open();
348348
fixture.detectChanges();
349+
flush();
349350

350351
testComponent.datepicker.close();
351352
fixture.detectChanges();
353+
flush();
352354
}
353355

354356
testComponent.datepicker.open();
355357
fixture.detectChanges();
358+
flush();
356359

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

0 commit comments

Comments
 (0)