Skip to content

Commit c92021e

Browse files
committed
fix: fix some glitches in the behavior of dropdown components
- avoid ExpressionChangedAfterItHasBeenCheckedError caused by use of `focused` in own template - fix displaying of all options when reopening
1 parent 482feae commit c92021e

File tree

4 files changed

+37
-13
lines changed

4 files changed

+37
-13
lines changed

src/app/core/admin/admin-entity-details/admin-entity-field/admin-entity-field.stories.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export default {
2424
{
2525
provide: MAT_DIALOG_DATA,
2626
useValue: {
27-
entitySchemaField: { id: null },
27+
entitySchemaField: { id: "name" },
28+
entityType: TestEntity,
2829
},
2930
},
3031
{ provide: MatDialogRef, useValue: null },

src/app/core/common-components/basic-autocomplete/basic-autocomplete.component.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!--Display-->
22
<input
33
*ngIf="display === 'text' || display === 'none'; else chipsDisplay"
4-
[hidden]="focused || display === 'none'"
4+
[hidden]="isInSearchMode() || display === 'none'"
55
[disabled]="_disabled"
66
matInput
77
style="text-overflow: ellipsis; width: calc(100% - 50px)"
@@ -13,7 +13,7 @@
1313

1414
<!--Search-->
1515
<input
16-
[hidden]="!focused"
16+
[hidden]="!isInSearchMode()"
1717
#inputElement
1818
[formControl]="autocompleteForm"
1919
matInput
@@ -31,7 +31,7 @@
3131
#autoSuggestions="matAutocomplete"
3232
(optionSelected)="select($event.option.value)"
3333
autoActiveFirstOption
34-
[hideSingleSelectionIndicator]="multi"
34+
[hideSingleSelectionIndicator]="true"
3535
>
3636
<div
3737
cdkDropList
@@ -43,11 +43,15 @@
4343
[style.height]="(autocompleteOptions?.length ?? 0) * 48 + 'px'"
4444
[style.max-height]="3 * 48 + 'px'"
4545
itemSize="48"
46+
minBufferPx="200"
4647
>
4748
<mat-option
4849
[value]="item"
4950
cdkDrag
50-
*cdkVirtualFor="let item of autocompleteOptions"
51+
*cdkVirtualFor="
52+
let item of autocompleteOptions;
53+
trackBy: trackByOptionValueFn
54+
"
5155
>
5256
<div class="flex-row disable-autocomplete-active-color align-center">
5357
<div *ngIf="reorder">

src/app/core/common-components/basic-autocomplete/basic-autocomplete.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,13 @@ describe("BasicAutocompleteComponent", () => {
163163

164164
component.showAutocomplete();
165165
expect(component.autocompleteForm).toHaveValue("");
166-
expect(component.focused).toBeTrue();
166+
expect(component.isInSearchMode()).toBeTrue();
167167

168168
component.onFocusOut({} as any);
169169
tick(200);
170170

171171
expect(component.displayText).toBe("some, values");
172-
expect(component.focused).toBeFalse();
172+
expect(component.isInSearchMode()).toBeFalse();
173173
}));
174174

175175
it("should update the error state if the form is invalid", () => {

src/app/core/common-components/basic-autocomplete/basic-autocomplete.component.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import {
99
Optional,
1010
Output,
1111
Self,
12+
signal,
1213
TemplateRef,
14+
TrackByFunction,
1315
ViewChild,
16+
WritableSignal,
1417
} from "@angular/core";
1518
import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from "@angular/common";
1619
import { MatFormFieldControl } from "@angular/material/form-field";
@@ -153,16 +156,30 @@ export class BasicAutocompleteComponent<O, V = O>
153156
@Input() set options(options: O[]) {
154157
this._options = options.map((o) => this.toSelectableOption(o));
155158
}
156-
retainSearchValue: string;
157159
private _options: SelectableOption<O, V>[] = [];
158160

159161
_selectedOptions: SelectableOption<O, V>[] = [];
160162

163+
/**
164+
* Keep the search value to help users quickly multi-select multiple related options without having to type filter text again
165+
*/
166+
retainSearchValue: string;
167+
161168
/**
162169
* Display the selected items as simple text, as chips or not at all (if used in combination with another component)
163170
*/
164171
@Input() display: "text" | "chips" | "none" = "text";
165172

173+
/**
174+
* display the search input rather than the selected elements only
175+
* (when the form field gets focused).
176+
*/
177+
isInSearchMode: WritableSignal<boolean> = signal(false);
178+
trackByOptionValueFn: TrackByFunction<SelectableOption<O, V>> | undefined = (
179+
i,
180+
o,
181+
) => o?.asValue;
182+
166183
constructor(
167184
elementRef: ElementRef<HTMLElement>,
168185
errorStateMatcher: ErrorStateMatcher,
@@ -232,9 +249,11 @@ export class BasicAutocompleteComponent<O, V = O>
232249
}
233250

234251
showAutocomplete(valueToRevertTo?: string) {
235-
if (this.retainSearchValue) {
252+
if (this.multi && this.retainSearchValue) {
253+
// reset the search value to previously entered text to help user selecting multiple similar options without retyping filter text
236254
this.autocompleteForm.setValue(this.retainSearchValue);
237255
} else {
256+
// reset the search value to show all available options again
238257
this.autocompleteForm.setValue("");
239258
}
240259
if (!this.multi) {
@@ -254,10 +273,10 @@ export class BasicAutocompleteComponent<O, V = O>
254273
}
255274
});
256275

257-
this.focus();
276+
this.isInSearchMode.set(true);
258277

259278
// update virtual scroll as the container remains empty until the user scrolls initially
260-
this.virtualScrollViewport.scrollToIndex(0);
279+
this.virtualScrollViewport.checkViewportSize();
261280
}
262281

263282
private updateAutocomplete(inputText: string): SelectableOption<O, V>[] {
@@ -344,7 +363,7 @@ export class BasicAutocompleteComponent<O, V = O>
344363
} else {
345364
this._selectedOptions = [option];
346365
this.value = option.asValue;
347-
this.blur();
366+
this.isInSearchMode.set(false);
348367
}
349368
}
350369

@@ -364,7 +383,7 @@ export class BasicAutocompleteComponent<O, V = O>
364383
if (!this.multi && this.autocompleteForm.value === "") {
365384
this.select(undefined);
366385
}
367-
this.blur();
386+
this.isInSearchMode.set(false);
368387
}
369388
}
370389

0 commit comments

Comments
 (0)