Skip to content

Commit

Permalink
refactor(docs-infra): Use new APIs in search dialog
Browse files Browse the repository at this point in the history
This updates the search dialog component to use signal APIs
  • Loading branch information
atscott committed Jul 31, 2024
1 parent 147eee4 commit 7984c11
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 68 deletions.
2 changes: 2 additions & 0 deletions adev/shared-docs/components/search-dialog/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ ts_library(
"//adev/shared-docs/services",
"//adev/shared-docs/testing",
"//packages/core",
"//packages/core/testing",
"//packages/platform-browser",
"//packages/router",
"//packages/router/testing",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ describe('SearchDialog', () => {

fakeSearch.searchResults.and.returnValue(fakeSearchResults);
fixture.detectChanges();
fixture.componentInstance.ngAfterViewInit();

fakeWindow.dispatchEvent(
new KeyboardEvent('keydown', {
Expand Down
119 changes: 52 additions & 67 deletions adev/shared-docs/components/search-dialog/search-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@
*/

import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
EventEmitter,
NgZone,
Injector,
OnDestroy,
OnInit,
Output,
QueryList,
ViewChild,
ViewChildren,
Signal,
afterNextRender,
effect,
inject,
output,
viewChild,
viewChildren,
} from '@angular/core';

import {WINDOW} from '../../providers/index';
Expand Down Expand Up @@ -53,95 +51,82 @@ import {RelativeLink} from '../../pipes/relative-link.pipe';
templateUrl: './search-dialog.component.html',
styleUrls: ['./search-dialog.component.scss'],
})
export class SearchDialog implements OnInit, AfterViewInit, OnDestroy {
@Output() onClose = new EventEmitter<void>();
@ViewChild('searchDialog') dialog?: ElementRef<HTMLDialogElement>;
@ViewChildren(SearchItem) items?: QueryList<SearchItem>;
export class SearchDialog implements OnDestroy {
onClose = output();
dialog = viewChild.required('searchDialog', {read: ElementRef}) as Signal<
ElementRef<HTMLDialogElement>
>;
items = viewChildren(SearchItem);

private readonly destroyRef = inject(DestroyRef);
private readonly ngZone = inject(NgZone);
private readonly search = inject(Search);
private readonly relativeLink = new RelativeLink();
private readonly router = inject(Router);
private readonly window = inject(WINDOW);

private keyManager?: ActiveDescendantKeyManager<SearchItem>;
private readonly injector = inject(Injector);
private readonly keyManager = new ActiveDescendantKeyManager(
this.items,
this.injector,
).withWrap();

searchQuery = this.search.searchQuery;
searchResults = this.search.searchResults;

ngOnInit(): void {
this.ngZone.runOutsideAngular(() => {
fromEvent<KeyboardEvent>(this.window, 'keydown')
.pipe(
filter((_) => !!this.keyManager),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((event) => {
// When user presses Enter we can navigate to currently selected item in the search result list.
if (event.key === 'Enter') {
this.navigateToTheActiveItem();
} else {
this.ngZone.run(() => {
this.keyManager?.onKeydown(event);
});
}
});
constructor() {
effect(() => {
this.items();
afterNextRender(
{
write: () => this.keyManager.setFirstItemActive(),
},
{injector: this.injector},
);
});
}

ngAfterViewInit() {
if (!this.dialog?.nativeElement.open) {
this.dialog?.nativeElement.showModal?.();
}

if (!this.items) {
return;
}
this.keyManager.change.pipe(takeUntilDestroyed()).subscribe(() => {
this.keyManager.activeItem?.scrollIntoView();
});

this.keyManager = new ActiveDescendantKeyManager(this.items).withWrap();
this.keyManager?.setFirstItemActive();
afterNextRender({
write: () => {
if (!this.dialog().nativeElement.open) {
this.dialog().nativeElement.showModal?.();
}
},
});

this.updateActiveItemWhenResultsChanged();
this.scrollToActiveItem();
fromEvent<KeyboardEvent>(this.window, 'keydown')
.pipe(takeUntilDestroyed())
.subscribe((event) => {
// When user presses Enter we can navigate to currently selected item in the search result list.
if (event.key === 'Enter') {
this.navigateToTheActiveItem();
} else {
this.keyManager.onKeydown(event);
}
});
}

ngOnDestroy(): void {
this.keyManager?.destroy();
this.keyManager.destroy();
}

closeSearchDialog() {
this.dialog?.nativeElement.close();
this.onClose.next();
this.dialog().nativeElement.close();
this.onClose.emit();
}

updateSearchQuery(query: string) {
this.search.updateSearchQuery(query);
}

private updateActiveItemWhenResultsChanged(): void {
this.items?.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
// Change detection should be run before execute `setFirstItemActive`.
Promise.resolve().then(() => {
this.keyManager?.setFirstItemActive();
});
});
}

private navigateToTheActiveItem(): void {
const activeItemLink: string | undefined = this.keyManager?.activeItem?.item?.url;
const activeItemLink: string | undefined = this.keyManager.activeItem?.item?.url;

if (!activeItemLink) {
return;
}

this.router.navigateByUrl(this.relativeLink.transform(activeItemLink));
this.onClose.next();
}

private scrollToActiveItem(): void {
this.keyManager?.change.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.keyManager?.activeItem?.scrollIntoView();
});
this.onClose.emit();
}
}

0 comments on commit 7984c11

Please sign in to comment.