|
7 | 7 | */
|
8 | 8 |
|
9 | 9 | import {
|
10 |
| - AfterViewInit, |
11 | 10 | ChangeDetectionStrategy,
|
12 | 11 | Component,
|
13 |
| - DestroyRef, |
14 | 12 | ElementRef,
|
15 |
| - EventEmitter, |
16 |
| - NgZone, |
| 13 | + Injector, |
17 | 14 | OnDestroy,
|
18 |
| - OnInit, |
19 |
| - Output, |
20 |
| - QueryList, |
21 |
| - ViewChild, |
22 |
| - ViewChildren, |
| 15 | + Signal, |
| 16 | + afterNextRender, |
| 17 | + effect, |
23 | 18 | inject,
|
| 19 | + output, |
| 20 | + viewChild, |
| 21 | + viewChildren, |
24 | 22 | } from '@angular/core';
|
25 | 23 |
|
26 | 24 | import {WINDOW} from '../../providers/index';
|
@@ -53,95 +51,82 @@ import {RelativeLink} from '../../pipes/relative-link.pipe';
|
53 | 51 | templateUrl: './search-dialog.component.html',
|
54 | 52 | styleUrls: ['./search-dialog.component.scss'],
|
55 | 53 | })
|
56 |
| -export class SearchDialog implements OnInit, AfterViewInit, OnDestroy { |
57 |
| - @Output() onClose = new EventEmitter<void>(); |
58 |
| - @ViewChild('searchDialog') dialog?: ElementRef<HTMLDialogElement>; |
59 |
| - @ViewChildren(SearchItem) items?: QueryList<SearchItem>; |
| 54 | +export class SearchDialog implements OnDestroy { |
| 55 | + onClose = output(); |
| 56 | + dialog = viewChild.required('searchDialog', {read: ElementRef}) as Signal< |
| 57 | + ElementRef<HTMLDialogElement> |
| 58 | + >; |
| 59 | + items = viewChildren(SearchItem); |
60 | 60 |
|
61 |
| - private readonly destroyRef = inject(DestroyRef); |
62 |
| - private readonly ngZone = inject(NgZone); |
63 | 61 | private readonly search = inject(Search);
|
64 | 62 | private readonly relativeLink = new RelativeLink();
|
65 | 63 | private readonly router = inject(Router);
|
66 | 64 | private readonly window = inject(WINDOW);
|
67 |
| - |
68 |
| - private keyManager?: ActiveDescendantKeyManager<SearchItem>; |
| 65 | + private readonly injector = inject(Injector); |
| 66 | + private readonly keyManager = new ActiveDescendantKeyManager( |
| 67 | + this.items, |
| 68 | + this.injector, |
| 69 | + ).withWrap(); |
69 | 70 |
|
70 | 71 | searchQuery = this.search.searchQuery;
|
71 | 72 | searchResults = this.search.searchResults;
|
72 | 73 |
|
73 |
| - ngOnInit(): void { |
74 |
| - this.ngZone.runOutsideAngular(() => { |
75 |
| - fromEvent<KeyboardEvent>(this.window, 'keydown') |
76 |
| - .pipe( |
77 |
| - filter((_) => !!this.keyManager), |
78 |
| - takeUntilDestroyed(this.destroyRef), |
79 |
| - ) |
80 |
| - .subscribe((event) => { |
81 |
| - // When user presses Enter we can navigate to currently selected item in the search result list. |
82 |
| - if (event.key === 'Enter') { |
83 |
| - this.navigateToTheActiveItem(); |
84 |
| - } else { |
85 |
| - this.ngZone.run(() => { |
86 |
| - this.keyManager?.onKeydown(event); |
87 |
| - }); |
88 |
| - } |
89 |
| - }); |
| 74 | + constructor() { |
| 75 | + effect(() => { |
| 76 | + this.items(); |
| 77 | + afterNextRender( |
| 78 | + { |
| 79 | + write: () => this.keyManager.setFirstItemActive(), |
| 80 | + }, |
| 81 | + {injector: this.injector}, |
| 82 | + ); |
90 | 83 | });
|
91 |
| - } |
92 |
| - |
93 |
| - ngAfterViewInit() { |
94 |
| - if (!this.dialog?.nativeElement.open) { |
95 |
| - this.dialog?.nativeElement.showModal?.(); |
96 |
| - } |
97 | 84 |
|
98 |
| - if (!this.items) { |
99 |
| - return; |
100 |
| - } |
| 85 | + this.keyManager.change.pipe(takeUntilDestroyed()).subscribe(() => { |
| 86 | + this.keyManager.activeItem?.scrollIntoView(); |
| 87 | + }); |
101 | 88 |
|
102 |
| - this.keyManager = new ActiveDescendantKeyManager(this.items).withWrap(); |
103 |
| - this.keyManager?.setFirstItemActive(); |
| 89 | + afterNextRender({ |
| 90 | + write: () => { |
| 91 | + if (!this.dialog().nativeElement.open) { |
| 92 | + this.dialog().nativeElement.showModal?.(); |
| 93 | + } |
| 94 | + }, |
| 95 | + }); |
104 | 96 |
|
105 |
| - this.updateActiveItemWhenResultsChanged(); |
106 |
| - this.scrollToActiveItem(); |
| 97 | + fromEvent<KeyboardEvent>(this.window, 'keydown') |
| 98 | + .pipe(takeUntilDestroyed()) |
| 99 | + .subscribe((event) => { |
| 100 | + // When user presses Enter we can navigate to currently selected item in the search result list. |
| 101 | + if (event.key === 'Enter') { |
| 102 | + this.navigateToTheActiveItem(); |
| 103 | + } else { |
| 104 | + this.keyManager.onKeydown(event); |
| 105 | + } |
| 106 | + }); |
107 | 107 | }
|
108 | 108 |
|
109 | 109 | ngOnDestroy(): void {
|
110 |
| - this.keyManager?.destroy(); |
| 110 | + this.keyManager.destroy(); |
111 | 111 | }
|
112 | 112 |
|
113 | 113 | closeSearchDialog() {
|
114 |
| - this.dialog?.nativeElement.close(); |
115 |
| - this.onClose.next(); |
| 114 | + this.dialog().nativeElement.close(); |
| 115 | + this.onClose.emit(); |
116 | 116 | }
|
117 | 117 |
|
118 | 118 | updateSearchQuery(query: string) {
|
119 | 119 | this.search.updateSearchQuery(query);
|
120 | 120 | }
|
121 | 121 |
|
122 |
| - private updateActiveItemWhenResultsChanged(): void { |
123 |
| - this.items?.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { |
124 |
| - // Change detection should be run before execute `setFirstItemActive`. |
125 |
| - Promise.resolve().then(() => { |
126 |
| - this.keyManager?.setFirstItemActive(); |
127 |
| - }); |
128 |
| - }); |
129 |
| - } |
130 |
| - |
131 | 122 | private navigateToTheActiveItem(): void {
|
132 |
| - const activeItemLink: string | undefined = this.keyManager?.activeItem?.item?.url; |
| 123 | + const activeItemLink: string | undefined = this.keyManager.activeItem?.item?.url; |
133 | 124 |
|
134 | 125 | if (!activeItemLink) {
|
135 | 126 | return;
|
136 | 127 | }
|
137 | 128 |
|
138 | 129 | this.router.navigateByUrl(this.relativeLink.transform(activeItemLink));
|
139 |
| - this.onClose.next(); |
140 |
| - } |
141 |
| - |
142 |
| - private scrollToActiveItem(): void { |
143 |
| - this.keyManager?.change.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { |
144 |
| - this.keyManager?.activeItem?.scrollIntoView(); |
145 |
| - }); |
| 130 | + this.onClose.emit(); |
146 | 131 | }
|
147 | 132 | }
|
0 commit comments