1
1
/*
2
- * Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
2
+ * Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
3
3
* This software is released under MIT license.
4
4
* The full license information can be found in LICENSE in the root directory of this project.
5
5
*/
6
6
7
7
import { isPlatformBrowser } from '@angular/common' ;
8
- import { Inject , Injectable , Optional , PLATFORM_ID , Renderer2 , SkipSelf } from '@angular/core' ;
8
+ import { Inject , Injectable , OnDestroy , Optional , PLATFORM_ID , Renderer2 , SkipSelf } from '@angular/core' ;
9
9
import { Observable , of , ReplaySubject } from 'rxjs' ;
10
10
import { map , take } from 'rxjs/operators' ;
11
11
import { ClrPopoverToggleService } from '../../../utils/popover/providers/popover-toggle.service' ;
@@ -18,7 +18,7 @@ import { Linkers } from '../../../utils/focus/focusable-item/linkers';
18
18
import { wrapObservable } from '../../../utils/focus/wrap-observable' ;
19
19
20
20
@Injectable ( )
21
- export class DropdownFocusHandler implements FocusableItem {
21
+ export class DropdownFocusHandler implements OnDestroy , FocusableItem {
22
22
constructor (
23
23
@Inject ( UNIQUE_ID ) public id : string ,
24
24
private renderer : Renderer2 ,
@@ -42,7 +42,7 @@ export class DropdownFocusHandler implements FocusableItem {
42
42
* If the dropdown was opened by clicking on the trigger, we automatically move to the first item
43
43
*/
44
44
moveToFirstItemWhenOpen ( ) {
45
- this . toggleService . openChange . subscribe ( open => {
45
+ const subscription = this . toggleService . openChange . subscribe ( open => {
46
46
if ( open && this . toggleService . originalEvent ) {
47
47
// Even if we properly waited for ngAfterViewInit, the container still wouldn't be attached to the DOM.
48
48
// So setTimeout is the only way to wait for the container to be ready to move focus to first item.
@@ -56,6 +56,8 @@ export class DropdownFocusHandler implements FocusableItem {
56
56
} ) ;
57
57
}
58
58
} ) ;
59
+
60
+ this . _unlistenFuncs . push ( ( ) => subscription . unsubscribe ( ) ) ;
59
61
}
60
62
61
63
private focusBackOnTrigger = false ;
@@ -64,7 +66,7 @@ export class DropdownFocusHandler implements FocusableItem {
64
66
* Focus on the menu when it opens, and focus back on the root trigger when the whole dropdown becomes closed
65
67
*/
66
68
handleRootFocus ( ) {
67
- this . toggleService . openChange . subscribe ( open => {
69
+ const subscription = this . toggleService . openChange . subscribe ( open => {
68
70
if ( ! open ) {
69
71
// We reset the state of the focus service both on initialization and when closing.
70
72
this . focusService . reset ( this ) ;
@@ -75,6 +77,8 @@ export class DropdownFocusHandler implements FocusableItem {
75
77
}
76
78
this . focusBackOnTrigger = open ;
77
79
} ) ;
80
+
81
+ this . _unlistenFuncs . push ( ( ) => subscription . unsubscribe ( ) ) ;
78
82
}
79
83
80
84
private _trigger : HTMLElement ;
@@ -205,8 +209,14 @@ export class DropdownFocusHandler implements FocusableItem {
205
209
}
206
210
207
211
ngOnDestroy ( ) {
208
- this . _unlistenFuncs . forEach ( ( unlisten : Function ) => unlisten ( ) ) ;
212
+ while ( this . _unlistenFuncs . length ) {
213
+ this . _unlistenFuncs . pop ( ) ( ) ;
214
+ }
209
215
this . focusService . detachListeners ( ) ;
216
+ // Caretaker note: we're explicitly setting these observables to `undefined` since they're
217
+ // created via `wrapObservable`. We provide the `onSubscribe` function, which captures `this`.
218
+ // This leads to a circular reference and prevents the `DropdownFocusHandler` from being GC'd.
219
+ this . right = this . down = this . up = undefined ;
210
220
}
211
221
}
212
222
0 commit comments