@@ -15,21 +15,36 @@ import {
15
15
Optional ,
16
16
SkipSelf ,
17
17
} from '@angular/core' ;
18
+ import { Platform } from '@angular/cdk/platform' ;
18
19
20
+ /**
21
+ * Whether we're in a testing environment.
22
+ * TODO(crisbeto): remove this once we have an overlay testing module.
23
+ */
24
+ const isTestEnvironment : boolean = typeof window !== 'undefined' && ! ! window &&
25
+ ! ! ( ( window as any ) . __karma__ || ( window as any ) . jasmine ) ;
19
26
20
27
/** Container inside which all overlays will render. */
21
28
@Injectable ( { providedIn : 'root' } )
22
29
export class OverlayContainer implements OnDestroy {
23
30
protected _containerElement : HTMLElement ;
24
31
protected _document : Document ;
25
32
26
- constructor ( @Inject ( DOCUMENT ) document : any ) {
33
+ constructor (
34
+ @Inject ( DOCUMENT ) document : any ,
35
+ /**
36
+ * @deprecated `platform` parameter to become required.
37
+ * @breaking -change 10.0.0
38
+ */
39
+ protected _platform ?: Platform ) {
27
40
this . _document = document ;
28
41
}
29
42
30
43
ngOnDestroy ( ) {
31
- if ( this . _containerElement && this . _containerElement . parentNode ) {
32
- this . _containerElement . parentNode . removeChild ( this . _containerElement ) ;
44
+ const container = this . _containerElement ;
45
+
46
+ if ( container && container . parentNode ) {
47
+ container . parentNode . removeChild ( container ) ;
33
48
}
34
49
}
35
50
@@ -52,16 +67,40 @@ export class OverlayContainer implements OnDestroy {
52
67
* with the 'cdk-overlay-container' class on the document body.
53
68
*/
54
69
protected _createContainer ( ) : void {
70
+ // @breaking -change 10.0.0 Remove null check for `_platform`.
71
+ const isBrowser = this . _platform ? this . _platform . isBrowser : typeof window !== 'undefined' ;
55
72
const containerClass = 'cdk-overlay-container' ;
56
- const previousContainers = this . _document . getElementsByClassName ( containerClass ) ;
57
73
58
- // Remove any old containers. This can happen when transitioning from the server to the client.
59
- for ( let i = 0 ; i < previousContainers . length ; i ++ ) {
60
- previousContainers [ i ] . parentNode ! . removeChild ( previousContainers [ i ] ) ;
74
+ if ( isBrowser || isTestEnvironment ) {
75
+ const oppositePlatformContainers =
76
+ this . _document . querySelectorAll ( `.${ containerClass } [platform="server"], ` +
77
+ `.${ containerClass } [platform="test"]` ) ;
78
+
79
+ // Remove any old containers from the opposite platform.
80
+ // This can happen when transitioning from the server to the client.
81
+ for ( let i = 0 ; i < oppositePlatformContainers . length ; i ++ ) {
82
+ oppositePlatformContainers [ i ] . parentNode ! . removeChild ( oppositePlatformContainers [ i ] ) ;
83
+ }
61
84
}
62
85
63
86
const container = this . _document . createElement ( 'div' ) ;
64
87
container . classList . add ( containerClass ) ;
88
+
89
+ // A long time ago we kept adding new overlay containers whenever a new app was instantiated,
90
+ // but at some point we added logic which clears the duplicate ones in order to avoid leaks.
91
+ // The new logic was a little too aggressive since it was breaking some legitimate use cases.
92
+ // To mitigate the problem we made it so that only containers from a different platform are
93
+ // cleared, but the side-effect was that people started depending on the overly-aggressive
94
+ // logic to clean up their tests for them. Until we can introduce an overlay-specific testing
95
+ // module which does the cleanup, we try to detect that we're in a test environment and we
96
+ // always clear the container. See #17006.
97
+ // TODO(crisbeto): remove the test environment check once we have an overlay testing module.
98
+ if ( isTestEnvironment ) {
99
+ container . setAttribute ( 'platform' , 'test' ) ;
100
+ } else if ( ! isBrowser ) {
101
+ container . setAttribute ( 'platform' , 'server' ) ;
102
+ }
103
+
65
104
this . _document . body . appendChild ( container ) ;
66
105
this . _containerElement = container ;
67
106
}
0 commit comments