From d1501bce5ca76cac71ed2fa2d3f6f7341f8e2439 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Mon, 5 May 2025 23:20:26 +0200 Subject: [PATCH] docs(docs-infra): preselect search text on re-open (#61129) PR Close #61129 --- .../search-dialog.component.html | 2 +- .../search-dialog/search-dialog.component.ts | 19 +++++++++++++++++-- .../text-field/text-field.component.html | 4 ++-- .../text-field/text-field.component.spec.ts | 9 +++++++++ .../search-item/search-item.directive.ts | 2 ++ adev/src/app/main.component.ts | 17 +++++++---------- 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.html b/adev/shared-docs/components/search-dialog/search-dialog.component.html index 397e35fdb5c..7f8cc4e45c7 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.html +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.html @@ -3,7 +3,7 @@ >('searchDialog'); items = viewChildren(SearchItem); + textField = viewChild(TextField); private readonly search = inject(Search); private readonly relativeLink = new RelativeLink(); @@ -67,7 +68,18 @@ export class SearchDialog implements OnDestroy { searchQuery = this.search.searchQuery; searchResults = this.search.searchResults; + // We use a FormControl instead of relying on NgModel+signal to avoid + // the issue https://github.com/angular/angular/issues/13568 + // TODO: Use signal forms when available + searchControl = new FormControl(this.searchQuery(), {nonNullable: true}); + constructor() { + this.searchControl.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { + this.searchQuery.set(value); + }); + + // Thinkig about refactoring this to a single afterRenderEffect ? + // Answer: It won't have the same behavior effect(() => { this.items(); afterNextRender( @@ -87,6 +99,9 @@ export class SearchDialog implements OnDestroy { if (!this.dialog().nativeElement.open) { this.dialog().nativeElement.showModal?.(); } + // We want to select the pre-existing text on opening + // In order to change the search input with minimal user interaction. + this.textField()?.input().nativeElement.select(); }, }); diff --git a/adev/shared-docs/components/text-field/text-field.component.html b/adev/shared-docs/components/text-field/text-field.component.html index c436af5b867..34941c460c0 100644 --- a/adev/shared-docs/components/text-field/text-field.component.html +++ b/adev/shared-docs/components/text-field/text-field.component.html @@ -6,8 +6,8 @@ type="text" [attr.placeholder]="placeholder()" [attr.name]="name()" - [ngModel]="value()" - (ngModelChange)="setValue($event)" + [value]="value()" + (input)="setValue(inputRef.value)" class="docs-text-field" /> diff --git a/adev/shared-docs/components/text-field/text-field.component.spec.ts b/adev/shared-docs/components/text-field/text-field.component.spec.ts index c79ee80780e..44814f086b4 100644 --- a/adev/shared-docs/components/text-field/text-field.component.spec.ts +++ b/adev/shared-docs/components/text-field/text-field.component.spec.ts @@ -28,4 +28,13 @@ describe('TextField', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should update DOM when setting the value via the CVA', () => { + component.setValue('test'); + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector('input').value).toBe('test'); + // If we were using ngModel instead of the value binding, we would get an empty string + // because of https://github.com/angular/angular/issues/13568 + }); }); diff --git a/adev/shared-docs/directives/search-item/search-item.directive.ts b/adev/shared-docs/directives/search-item/search-item.directive.ts index 7e8f185baa4..c7e8c9e6a8e 100644 --- a/adev/shared-docs/directives/search-item/search-item.directive.ts +++ b/adev/shared-docs/directives/search-item/search-item.directive.ts @@ -17,6 +17,8 @@ import {SearchResultItem} from '../../interfaces'; }, }) export class SearchItem implements Highlightable { + // Those inputs are required by the Highlightable interface + // We can't migrate them to signals yet @Input() item?: SearchResultItem; @Input() disabled = false; diff --git a/adev/src/app/main.component.ts b/adev/src/app/main.component.ts index 7fde6fe3117..03b3b36b3d2 100644 --- a/adev/src/app/main.component.ts +++ b/adev/src/app/main.component.ts @@ -22,15 +22,12 @@ export default class MainComponent { search = model(''); constructor() { - effect( - () => { - const search = this.search(); - if (search !== undefined) { - this.displaySearchDialog.set(true); - this.searchService.searchQuery.set(search); - } - }, - {allowSignalWrites: true}, - ); + effect(() => { + const search = this.search(); + if (search !== undefined) { + this.displaySearchDialog.set(true); + this.searchService.searchQuery.set(search); + } + }); } }