Skip to content

Commit abec28d

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 04234f0 commit abec28d

File tree

5 files changed

+114
-18
lines changed

5 files changed

+114
-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
@@ -52,6 +52,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
5252
};
5353
});
5454

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

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

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

7684
this._positionStrategy = _config.positionStrategy;
85+
this._animationsDisabled = animationMode === 'NoopAnimations';
7786
}
7887

7988
/** The overlay's HTML element */
@@ -354,6 +363,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
354363
this._backdropElement = this._document.createElement('div');
355364
this._backdropElement.classList.add('cdk-overlay-backdrop');
356365

366+
if (this._animationsDisabled) {
367+
this._backdropElement.classList.add('cdk-overlay-backdrop-transition-disabled');
368+
}
369+
357370
if (this._config.backdropClass) {
358371
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
359372
}
@@ -398,24 +411,32 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
398411
detachBackdrop(): void {
399412
let backdropToDetach = this._backdropElement;
400413

401-
if (backdropToDetach) {
402-
let timeoutId: number;
403-
let finishDetach = () => {
404-
// It may not be attached to anything in certain cases (e.g. unit tests).
405-
if (backdropToDetach && backdropToDetach.parentNode) {
406-
backdropToDetach.parentNode.removeChild(backdropToDetach);
407-
}
414+
if (!backdropToDetach) {
415+
return;
416+
}
408417

409-
// It is possible that a new portal has been attached to this overlay since we started
410-
// removing the backdrop. If that is the case, only clear the backdrop reference if it
411-
// is still the same instance that we started to remove.
412-
if (this._backdropElement == backdropToDetach) {
413-
this._backdropElement = null;
414-
}
418+
let timeoutId: number;
419+
const finishDetach = () => {
420+
// It may not be attached to anything in certain cases (e.g. unit tests).
421+
if (backdropToDetach && backdropToDetach.parentNode) {
422+
backdropToDetach.parentNode.removeChild(backdropToDetach);
423+
}
424+
425+
// It is possible that a new portal has been attached to this overlay since we started
426+
// removing the backdrop. If that is the case, only clear the backdrop reference if it
427+
// is still the same instance that we started to remove.
428+
if (this._backdropElement == backdropToDetach) {
429+
this._backdropElement = null;
430+
}
415431

416-
clearTimeout(timeoutId);
417-
};
432+
clearTimeout(timeoutId);
433+
};
418434

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

421442
if (this._config.backdropClass) {

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)