Skip to content

Commit 6e94dcc

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 6d7f417 commit 6e94dcc

File tree

5 files changed

+108
-14
lines changed

5 files changed

+108
-14
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: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
5353
};
5454
});
5555

56+
/** Whether animations are disabled for this overlay. */
57+
private readonly _animationsDisabled: boolean;
58+
5659
/** Stream of keydown events dispatched to this overlay. */
5760
_keydownEvents = new Subject<KeyboardEvent>();
5861

@@ -68,13 +71,19 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
6871
private _keyboardDispatcher: OverlayKeyboardDispatcher,
6972
private _document: Document,
7073
// @breaking-change 8.0.0 `_location` parameter to be made required.
71-
private _location?: Location) {
74+
private _location?: Location,
75+
/**
76+
* @deprecated `animationMode` parameter to be made required.
77+
* @breaking-change 8.0.0
78+
*/
79+
animationMode?: string) {
7280

7381
if (_config.scrollStrategy) {
7482
_config.scrollStrategy.attach(this);
7583
}
7684

7785
this._positionStrategy = _config.positionStrategy;
86+
this._animationsDisabled = animationMode === 'NoopAnimations';
7887
}
7988

8089
/** The overlay's HTML element */
@@ -351,6 +360,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
351360
this._backdropElement = this._document.createElement('div');
352361
this._backdropElement.classList.add('cdk-overlay-backdrop');
353362

363+
if (this._animationsDisabled) {
364+
this._backdropElement.classList.add('cdk-overlay-backdrop-transition-disabled');
365+
}
366+
354367
if (this._config.backdropClass) {
355368
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
356369
}
@@ -420,20 +433,26 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
420433
clearTimeout(timeoutId);
421434
};
422435

423-
backdropToDetach.classList.remove('cdk-overlay-backdrop-showing');
436+
if (this._animationsDisabled) {
437+
// When the animations are disabled via the `NoopAnimationsModule`, we can be
438+
// fairly certain that they won't happen so we can remove the element immediately.
439+
finishDetach();
440+
} else {
441+
backdropToDetach.classList.remove('cdk-overlay-backdrop-showing');
424442

425-
this._ngZone.runOutsideAngular(() => {
426-
backdropToDetach!.addEventListener('transitionend', finishDetach);
427-
});
443+
this._ngZone.runOutsideAngular(() => {
444+
backdropToDetach!.addEventListener('transitionend', finishDetach);
445+
});
428446

429-
// If the backdrop doesn't have a transition, the `transitionend` event won't fire.
430-
// In this case we make it unclickable and we try to remove it after a delay.
431-
backdropToDetach.style.pointerEvents = 'none';
447+
// If the backdrop doesn't have a transition, the `transitionend` event won't fire.
448+
// In this case we make it unclickable and we try to remove it after a delay.
449+
backdropToDetach.style.pointerEvents = 'none';
432450

433-
// Run this outside the Angular zone because there's nothing that Angular cares about.
434-
// If it were to run inside the Angular zone, every test that used Overlay would have to be
435-
// either async or fakeAsync.
436-
timeoutId = this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500));
451+
// Run this outside the Angular zone because there's nothing that Angular cares about.
452+
// If it were to run inside the Angular zone, every test that used Overlay would have to be
453+
// either async or fakeAsync.
454+
timeoutId = this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500));
455+
}
437456
}
438457

439458
/** Toggles a single CSS class or an array of classes on an element. */

src/cdk/overlay/overlay.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
ScrollStrategy,
3030
} from './index';
3131
import {OverlayReference} from './overlay-reference';
32+
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
3233

3334

3435
describe('Overlay', () => {
@@ -683,6 +684,64 @@ describe('Overlay', () => {
683684
expect(backdrop.classList).toContain('cdk-overlay-transparent-backdrop');
684685
});
685686

687+
it('should not disable the backdrop transition if animations are enabled', () => {
688+
overlayContainer.ngOnDestroy();
689+
690+
TestBed
691+
.resetTestingModule()
692+
.configureTestingModule({
693+
imports: [OverlayModule, PortalModule, OverlayTestModule, BrowserAnimationsModule],
694+
})
695+
.compileComponents();
696+
697+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
698+
overlay = o;
699+
overlayContainer = oc;
700+
overlayContainerElement = oc.getContainerElement();
701+
})();
702+
703+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
704+
fixture.detectChanges();
705+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
706+
viewContainerFixture = fixture;
707+
708+
let overlayRef = overlay.create(config);
709+
overlayRef.attach(componentPortal);
710+
viewContainerFixture.detectChanges();
711+
712+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
713+
expect(backdrop.classList).not.toContain('cdk-overlay-backdrop-transition-disabled');
714+
});
715+
716+
it('should disable the backdrop transition when animations are disabled', () => {
717+
overlayContainer.ngOnDestroy();
718+
719+
TestBed
720+
.resetTestingModule()
721+
.configureTestingModule({
722+
imports: [OverlayModule, PortalModule, OverlayTestModule, NoopAnimationsModule],
723+
})
724+
.compileComponents();
725+
726+
inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
727+
overlay = o;
728+
overlayContainer = oc;
729+
overlayContainerElement = oc.getContainerElement();
730+
})();
731+
732+
let fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
733+
fixture.detectChanges();
734+
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef);
735+
viewContainerFixture = fixture;
736+
737+
let overlayRef = overlay.create(config);
738+
overlayRef.attach(componentPortal);
739+
viewContainerFixture.detectChanges();
740+
741+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
742+
expect(backdrop.classList).toContain('cdk-overlay-backdrop-transition-disabled');
743+
});
744+
686745
it('should apply multiple custom classes to the overlay', () => {
687746
config.backdropClass = ['custom-class-1', 'custom-class-2'];
688747

src/cdk/overlay/overlay.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {OverlayContainer} from './overlay-container';
2424
import {OverlayRef} from './overlay-ref';
2525
import {OverlayPositionBuilder} from './position/overlay-position-builder';
2626
import {ScrollStrategyOptions} from './scroll/index';
27+
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
2728

2829

2930
/** Next overlay unique ID. */
@@ -56,7 +57,12 @@ export class Overlay {
5657
@Inject(DOCUMENT) private _document: any,
5758
private _directionality: Directionality,
5859
// @breaking-change 8.0.0 `_location` parameter to be made required.
59-
@Optional() private _location?: Location) { }
60+
@Optional() private _location?: Location,
61+
/**
62+
* @deprecated `animationMode` parameter to be made required.
63+
* @breaking-change 8.0.0
64+
*/
65+
@Optional() @Inject(ANIMATION_MODULE_TYPE) private _animationMode?: string) { }
6066

6167
/**
6268
* Creates an overlay.
@@ -72,7 +78,7 @@ export class Overlay {
7278
overlayConfig.direction = overlayConfig.direction || this._directionality.value;
7379

7480
return new OverlayRef(portalOutlet, host, pane, overlayConfig, this._ngZone,
75-
this._keyboardDispatcher, this._document, this._location);
81+
this._keyboardDispatcher, this._document, this._location, this._animationMode);
7682
}
7783

7884
/**

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)