@@ -17,6 +17,7 @@ import {
1717 Input ,
1818 NgZone ,
1919 OnDestroy ,
20+ DoCheck ,
2021} from '@angular/core' ;
2122import { take } from 'rxjs/operators' ;
2223import { InteractivityChecker } from '../interactivity-checker/interactivity-checker' ;
@@ -32,6 +33,7 @@ import {InteractivityChecker} from '../interactivity-checker/interactivity-check
3233export class FocusTrap {
3334 private _startAnchor : HTMLElement | null ;
3435 private _endAnchor : HTMLElement | null ;
36+ private _hasAttached = false ;
3537
3638 /** Whether the focus trap is active. */
3739 get enabled ( ) : boolean { return this . _enabled ; }
@@ -72,30 +74,34 @@ export class FocusTrap {
7274 /**
7375 * Inserts the anchors into the DOM. This is usually done automatically
7476 * 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.
7579 */
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 ;
8384 }
8485
8586 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+ }
9391
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 ( ) ) ;
9795 }
9896 } ) ;
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 ;
99105 }
100106
101107 /**
@@ -217,6 +223,13 @@ export class FocusTrap {
217223 return ! ! redirectToElement ;
218224 }
219225
226+ /**
227+ * Checks whether the focus trap has successfuly been attached.
228+ */
229+ hasAttached ( ) : boolean {
230+ return this . _hasAttached ;
231+ }
232+
220233 /** Get the first tabbable element from a DOM subtree (inclusive). */
221234 private _getFirstTabbableElement ( root : HTMLElement ) : HTMLElement | null {
222235 if ( this . _checker . isFocusable ( root ) && this . _checker . isTabbable ( root ) ) {
@@ -313,7 +326,7 @@ export class FocusTrapFactory {
313326 selector : '[cdkTrapFocus]' ,
314327 exportAs : 'cdkTrapFocus' ,
315328} )
316- export class CdkTrapFocus implements OnDestroy , AfterContentInit {
329+ export class CdkTrapFocus implements OnDestroy , AfterContentInit , DoCheck {
317330 private _document : Document ;
318331
319332 /** Underlying FocusTrap instance. */
@@ -364,4 +377,10 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit {
364377 this . focusTrap . focusInitialElementWhenReady ( ) ;
365378 }
366379 }
380+
381+ ngDoCheck ( ) {
382+ if ( ! this . focusTrap . hasAttached ( ) ) {
383+ this . focusTrap . attachAnchors ( ) ;
384+ }
385+ }
367386}
0 commit comments