@@ -17,6 +17,7 @@ import {
17
17
Input ,
18
18
NgZone ,
19
19
OnDestroy ,
20
+ DoCheck ,
20
21
} from '@angular/core' ;
21
22
import { take } from 'rxjs/operators' ;
22
23
import { InteractivityChecker } from '../interactivity-checker/interactivity-checker' ;
@@ -32,6 +33,7 @@ import {InteractivityChecker} from '../interactivity-checker/interactivity-check
32
33
export class FocusTrap {
33
34
private _startAnchor : HTMLElement | null ;
34
35
private _endAnchor : HTMLElement | null ;
36
+ private _hasAttached = false ;
35
37
36
38
/** Whether the focus trap is active. */
37
39
get enabled ( ) : boolean { return this . _enabled ; }
@@ -72,30 +74,34 @@ export class FocusTrap {
72
74
/**
73
75
* Inserts the anchors into the DOM. This is usually done automatically
74
76
* in the constructor, but can be deferred for cases like directives with `*ngIf`.
77
+ * @returns Whether the focus trap managed to attach successfuly. This may not be the case
78
+ * if the target element isn't currently in the DOM.
75
79
*/
76
- attachAnchors ( ) : void {
77
- if ( ! this . _startAnchor ) {
78
- this . _startAnchor = this . _createAnchor ( ) ;
79
- }
80
-
81
- if ( ! this . _endAnchor ) {
82
- this . _endAnchor = this . _createAnchor ( ) ;
80
+ attachAnchors ( ) : boolean {
81
+ // If we're not on the browser, there can be no focus to trap.
82
+ if ( this . _hasAttached ) {
83
+ return true ;
83
84
}
84
85
85
86
this . _ngZone . runOutsideAngular ( ( ) => {
86
- this . _startAnchor ! . addEventListener ( 'focus' , ( ) => {
87
- this . focusLastTabbableElement ( ) ;
88
- } ) ;
89
-
90
- this . _endAnchor ! . addEventListener ( 'focus' , ( ) => {
91
- this . focusFirstTabbableElement ( ) ;
92
- } ) ;
87
+ if ( ! this . _startAnchor ) {
88
+ this . _startAnchor = this . _createAnchor ( ) ;
89
+ this . _startAnchor ! . addEventListener ( 'focus' , ( ) => this . focusLastTabbableElement ( ) ) ;
90
+ }
93
91
94
- if ( this . _element . parentNode ) {
95
- this . _element . parentNode . insertBefore ( this . _startAnchor ! , this . _element ) ;
96
- this . _element . parentNode . insertBefore ( this . _endAnchor ! , this . _element . nextSibling ) ;
92
+ if ( ! this . _endAnchor ) {
93
+ this . _endAnchor = this . _createAnchor ( ) ;
94
+ this . _endAnchor ! . addEventListener ( 'focus' , ( ) => this . focusFirstTabbableElement ( ) ) ;
97
95
}
98
96
} ) ;
97
+
98
+ if ( this . _element . parentNode ) {
99
+ this . _element . parentNode . insertBefore ( this . _startAnchor ! , this . _element ) ;
100
+ this . _element . parentNode . insertBefore ( this . _endAnchor ! , this . _element . nextSibling ) ;
101
+ this . _hasAttached = true ;
102
+ }
103
+
104
+ return this . _hasAttached ;
99
105
}
100
106
101
107
/**
@@ -217,6 +223,13 @@ export class FocusTrap {
217
223
return ! ! redirectToElement ;
218
224
}
219
225
226
+ /**
227
+ * Checks whether the focus trap has successfuly been attached.
228
+ */
229
+ hasAttached ( ) : boolean {
230
+ return this . _hasAttached ;
231
+ }
232
+
220
233
/** Get the first tabbable element from a DOM subtree (inclusive). */
221
234
private _getFirstTabbableElement ( root : HTMLElement ) : HTMLElement | null {
222
235
if ( this . _checker . isFocusable ( root ) && this . _checker . isTabbable ( root ) ) {
@@ -313,7 +326,7 @@ export class FocusTrapFactory {
313
326
selector : '[cdkTrapFocus]' ,
314
327
exportAs : 'cdkTrapFocus' ,
315
328
} )
316
- export class CdkTrapFocus implements OnDestroy , AfterContentInit {
329
+ export class CdkTrapFocus implements OnDestroy , AfterContentInit , DoCheck {
317
330
private _document : Document ;
318
331
319
332
/** Underlying FocusTrap instance. */
@@ -364,4 +377,10 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit {
364
377
this . focusTrap . focusInitialElementWhenReady ( ) ;
365
378
}
366
379
}
380
+
381
+ ngDoCheck ( ) {
382
+ if ( ! this . focusTrap . hasAttached ( ) ) {
383
+ this . focusTrap . attachAnchors ( ) ;
384
+ }
385
+ }
367
386
}
0 commit comments