Skip to content

Commit fc219c8

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 3bc52df commit fc219c8

File tree

5 files changed

+115
-18
lines changed

5 files changed

+115
-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: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
5050
};
5151
});
5252

53+
/** Whether animations are disabled for this overlay. */
54+
private readonly _animationsDisabled: boolean;
55+
5356
/** Stream of keydown events dispatched to this overlay. */
5457
_keydownEvents = new Subject<KeyboardEvent>();
5558

@@ -63,13 +66,19 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
6366
private _config: ImmutableObject<OverlayConfig>,
6467
private _ngZone: NgZone,
6568
private _keyboardDispatcher: OverlayKeyboardDispatcher,
66-
private _document: Document) {
69+
private _document: Document,
70+
/**
71+
* @deprecated `animationMode` parameter to be made required.
72+
* @breaking-change 8.0.0
73+
*/
74+
animationMode?: string) {
6775

6876
if (_config.scrollStrategy) {
6977
_config.scrollStrategy.attach(this);
7078
}
7179

7280
this._positionStrategy = _config.positionStrategy;
81+
this._animationsDisabled = animationMode === 'NoopAnimations';
7382
}
7483

7584
/** The overlay's HTML element */
@@ -340,6 +349,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
340349
this._backdropElement = this._document.createElement('div');
341350
this._backdropElement.classList.add('cdk-overlay-backdrop');
342351

352+
if (this._animationsDisabled) {
353+
this._backdropElement.classList.add('cdk-overlay-backdrop-transition-disabled');
354+
}
355+
343356
if (this._config.backdropClass) {
344357
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
345358
}
@@ -384,24 +397,32 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
384397
detachBackdrop(): void {
385398
let backdropToDetach = this._backdropElement;
386399

387-
if (backdropToDetach) {
388-
let timeoutId: number;
389-
let finishDetach = () => {
390-
// It may not be attached to anything in certain cases (e.g. unit tests).
391-
if (backdropToDetach && backdropToDetach.parentNode) {
392-
backdropToDetach.parentNode.removeChild(backdropToDetach);
393-
}
400+
if (!backdropToDetach) {
401+
return;
402+
}
394403

395-
// It is possible that a new portal has been attached to this overlay since we started
396-
// removing the backdrop. If that is the case, only clear the backdrop reference if it
397-
// is still the same instance that we started to remove.
398-
if (this._backdropElement == backdropToDetach) {
399-
this._backdropElement = null;
400-
}
404+
let timeoutId: number;
405+
const finishDetach = () => {
406+
// It may not be attached to anything in certain cases (e.g. unit tests).
407+
if (backdropToDetach && backdropToDetach.parentNode) {
408+
backdropToDetach.parentNode.removeChild(backdropToDetach);
409+
}
410+
411+
// It is possible that a new portal has been attached to this overlay since we started
412+
// removing the backdrop. If that is the case, only clear the backdrop reference if it
413+
// is still the same instance that we started to remove.
414+
if (this._backdropElement == backdropToDetach) {
415+
this._backdropElement = null;
416+
}
401417

402-
clearTimeout(timeoutId);
403-
};
418+
clearTimeout(timeoutId);
419+
};
404420

421+
if (this._animationsDisabled) {
422+
// When the animations are disabled via the `NoopAnimationsModule`, we can be
423+
// fairly certain that they won't happen so we can remove the element immediately.
424+
finishDetach();
425+
} else {
405426
backdropToDetach.classList.remove('cdk-overlay-backdrop-showing');
406427

407428
if (this._config.backdropClass) {

src/cdk/overlay/overlay.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
ScrollStrategy,
2828
} from './index';
2929
import {OverlayReference} from './overlay-reference';
30+
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
3031

3132

3233
describe('Overlay', () => {
@@ -663,6 +664,64 @@ describe('Overlay', () => {
663664
expect(backdrop.classList).toContain('cdk-overlay-transparent-backdrop');
664665
});
665666

667+
it('should not disable the backdrop transition if animations are enabled', () => {
668+
overlayContainer.ngOnDestroy();
669+
670+
TestBed
671+
.resetTestingModule()
672+
.configureTestingModule({
673+
imports: [OverlayModule, PortalModule, OverlayTestModule, BrowserAnimationsModule],
674+
})
675+
.compileComponents();
676+
677+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
678+
overlay = o;
679+
overlayContainer = oc;
680+
overlayContainerElement = oc.getContainerElement();
681+
})();
682+
683+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
684+
fixture.detectChanges();
685+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
686+
viewContainerFixture = fixture;
687+
688+
let overlayRef = overlay.create(config);
689+
overlayRef.attach(componentPortal);
690+
viewContainerFixture.detectChanges();
691+
692+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
693+
expect(backdrop.classList).not.toContain('cdk-overlay-backdrop-transition-disabled');
694+
});
695+
696+
it('should disable the backdrop transition when animations are disabled', () => {
697+
overlayContainer.ngOnDestroy();
698+
699+
TestBed
700+
.resetTestingModule()
701+
.configureTestingModule({
702+
imports: [OverlayModule, PortalModule, OverlayTestModule, NoopAnimationsModule],
703+
})
704+
.compileComponents();
705+
706+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
707+
overlay = o;
708+
overlayContainer = oc;
709+
overlayContainerElement = oc.getContainerElement();
710+
})();
711+
712+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
713+
fixture.detectChanges();
714+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
715+
viewContainerFixture = fixture;
716+
717+
let overlayRef = overlay.create(config);
718+
overlayRef.attach(componentPortal);
719+
viewContainerFixture.detectChanges();
720+
721+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
722+
expect(backdrop.classList).toContain('cdk-overlay-backdrop-transition-disabled');
723+
});
724+
666725
it('should apply multiple custom classes to the overlay', () => {
667726
config.backdropClass = ['custom-class-1', 'custom-class-2'];
668727

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
@@ -379,13 +379,16 @@ describe('MatDatepicker', () => {
379379
for (let i = 0; i < 3; i++) {
380380
testComponent.datepicker.open();
381381
fixture.detectChanges();
382+
flush();
382383

383384
testComponent.datepicker.close();
384385
fixture.detectChanges();
386+
flush();
385387
}
386388

387389
testComponent.datepicker.open();
388390
fixture.detectChanges();
391+
flush();
389392

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

0 commit comments

Comments
 (0)