@@ -12,7 +12,7 @@ import {
1212 Input ,
1313 NgZone ,
1414 OnDestroy ,
15- AfterContentInit ,
15+ DoCheck ,
1616 Injectable ,
1717} from '@angular/core' ;
1818import { coerceBooleanProperty } from '@angular/cdk/coercion' ;
@@ -32,6 +32,7 @@ import {InteractivityChecker} from './interactivity-checker';
3232export class FocusTrap {
3333 private _startAnchor : HTMLElement | null ;
3434 private _endAnchor : HTMLElement | null ;
35+ private _hasAttached = false ;
3536
3637 /** Whether the focus trap is active. */
3738 get enabled ( ) : boolean { return this . _enabled ; }
@@ -72,35 +73,35 @@ export class FocusTrap {
7273 /**
7374 * Inserts the anchors into the DOM. This is usually done automatically
7475 * in the constructor, but can be deferred for cases like directives with `*ngIf`.
76+ * @returns Whether the focus trap managed to attach successfuly. This may not be the case
77+ * if the target element isn't currently in the DOM.
7578 */
76- attachAnchors ( ) : void {
79+ attachAnchors ( ) : boolean {
7780 // If we're not on the browser, there can be no focus to trap.
78- if ( ! this . _platform . isBrowser ) {
79- return ;
80- }
81-
82- if ( ! this . _startAnchor ) {
83- this . _startAnchor = this . _createAnchor ( ) ;
84- }
85-
86- if ( ! this . _endAnchor ) {
87- this . _endAnchor = this . _createAnchor ( ) ;
81+ if ( ! this . _platform . isBrowser || this . _hasAttached ) {
82+ this . _hasAttached = true ;
83+ return true ;
8884 }
8985
9086 this . _ngZone . runOutsideAngular ( ( ) => {
91- this . _startAnchor ! . addEventListener ( 'focus' , ( ) => {
92- this . focusLastTabbableElement ( ) ;
93- } ) ;
94-
95- this . _endAnchor ! . addEventListener ( 'focus' , ( ) => {
96- this . focusFirstTabbableElement ( ) ;
97- } ) ;
87+ if ( ! this . _startAnchor ) {
88+ this . _startAnchor = this . _createAnchor ( ) ;
89+ this . _startAnchor ! . addEventListener ( 'focus' , ( ) => this . focusLastTabbableElement ( ) ) ;
90+ }
9891
99- if ( this . _element . parentNode ) {
100- this . _element . parentNode . insertBefore ( this . _startAnchor ! , this . _element ) ;
101- 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 ( ) ) ;
10295 }
10396 } ) ;
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 ;
104105 }
105106
106107 /**
@@ -206,6 +207,13 @@ export class FocusTrap {
206207 return ! ! redirectToElement ;
207208 }
208209
210+ /**
211+ * Checks whether the focus trap has successfuly been attached.
212+ */
213+ hasAttached ( ) : boolean {
214+ return this . _hasAttached ;
215+ }
216+
209217 /** Get the first tabbable element from a DOM subtree (inclusive). */
210218 private _getFirstTabbableElement ( root : HTMLElement ) : HTMLElement | null {
211219 if ( this . _checker . isFocusable ( root ) && this . _checker . isTabbable ( root ) ) {
@@ -287,12 +295,12 @@ export class FocusTrapFactory {
287295
288296/**
289297 * Directive for trapping focus within a region.
290- * @deprecated
298+ * @deprecated Use `cdkTrapFocus` instead.
291299 */
292300@Directive ( {
293301 selector : 'cdk-focus-trap' ,
294302} )
295- export class FocusTrapDeprecatedDirective implements OnDestroy , AfterContentInit {
303+ export class FocusTrapDeprecatedDirective implements OnDestroy , DoCheck {
296304 focusTrap : FocusTrap ;
297305
298306 /** Whether the focus trap is active. */
@@ -310,8 +318,10 @@ export class FocusTrapDeprecatedDirective implements OnDestroy, AfterContentInit
310318 this . focusTrap . destroy ( ) ;
311319 }
312320
313- ngAfterContentInit ( ) {
314- this . focusTrap . attachAnchors ( ) ;
321+ ngDoCheck ( ) {
322+ if ( ! this . focusTrap . hasAttached ( ) ) {
323+ this . focusTrap . attachAnchors ( ) ;
324+ }
315325 }
316326}
317327
@@ -321,7 +331,7 @@ export class FocusTrapDeprecatedDirective implements OnDestroy, AfterContentInit
321331 selector : '[cdkTrapFocus]' ,
322332 exportAs : 'cdkTrapFocus' ,
323333} )
324- export class FocusTrapDirective implements OnDestroy , AfterContentInit {
334+ export class FocusTrapDirective implements OnDestroy , DoCheck {
325335 focusTrap : FocusTrap ;
326336
327337 /** Whether the focus trap is active. */
@@ -337,7 +347,11 @@ export class FocusTrapDirective implements OnDestroy, AfterContentInit {
337347 this . focusTrap . destroy ( ) ;
338348 }
339349
340- ngAfterContentInit ( ) {
341- this . focusTrap . attachAnchors ( ) ;
350+ ngDoCheck ( ) {
351+ // Since the element may not be attached to the DOM (or another element)
352+ // immediately, keep trying until it is.
353+ if ( ! this . focusTrap . hasAttached ( ) ) {
354+ this . focusTrap . attachAnchors ( ) ;
355+ }
342356 }
343357}
0 commit comments