Skip to content

Commit

Permalink
fix Automatically select first entry in autocomplete (#1457)
Browse files Browse the repository at this point in the history
closes #1317

Co-authored-by: Simon <[email protected]>
  • Loading branch information
christophscheuing and TheSlimvReal authored Oct 20, 2022
1 parent 677c7b6 commit 7077698
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
We also set floatLabel to always because the label is attached to the wrong input
-->
<input
matInput
[formControl]="formControl"
hidden
/>
<input matInput [formControl]="formControl" hidden />
<mat-label>{{ label }}</mat-label>
<!--
This input contains the display value (i.e. the value that the user chooses and sees when he clicks 'update')
Expand All @@ -29,22 +25,29 @@
(keyup)="updateAutocomplete(inputElement.value)"
(focusin)="updateAutocomplete(inputElement.value)"
(focusout)="select(inputElement.value)"
>
/>
<mat-autocomplete
#autoSuggestions="matAutocomplete"
(optionSelected)="select($event.option.value)"
autoActiveFirstOption
>
<!-- Optional header -->
<ng-content select="mat-option"></ng-content>
<mat-option *ngFor="let entity of autocompleteEntities | async" [value]="entity">
<mat-option
*ngFor="let entity of autocompleteEntities | async"
[value]="entity"
>
<app-display-entity
[entityToDisplay]="entity"
[linkDisabled]="true"
></app-display-entity>
</mat-option>
</mat-autocomplete>

<mat-error *ngIf="formControl.errors?.required" i18n="Error message for any input">
<mat-error
*ngIf="formControl.errors?.required"
i18n="Error message for any input"
>
This field is required
</mat-error>
</mat-form-field>
Expand All @@ -54,7 +57,11 @@
class="block-wrapper"
[entityToDisplay]="selectedEntity"
></app-display-entity>
<button *ngIf="formControl.enabled" mat-icon-button (click)="editSelectedEntity()">
<button
*ngIf="formControl.enabled"
mat-icon-button
(click)="editSelectedEntity()"
>
<fa-icon icon="pen"></fa-icon>
</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@
<mat-autocomplete
#autoSuggestions="matAutocomplete"
(optionSelected)="selectEntity($event.option.value)"
autoActiveFirstOption
>
<!-- Optional header -->
<ng-content select="mat-option"></ng-content>
<mat-option *ngFor="let res of filteredEntities | async" [value]="res">
<mat-option *ngFor="let res of filteredEntities" [value]="res">
<app-display-entity
[entityToDisplay]="res"
[linkDisabled]="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { mockEntityMapper } from "../../../entity/mock-entity-mapper-service";
import { User } from "../../../user/user";
import { Child } from "../../../../child-dev-project/children/model/child";
import { FlexLayoutModule } from "@angular/flex-layout";
import { firstValueFrom, Subscription } from "rxjs";
import { Subscription } from "rxjs";
import { EntitySchemaService } from "../../../entity/schema/entity-schema.service";
import {
EntityRegistry,
Expand Down Expand Up @@ -87,14 +87,12 @@ describe("EntitySelectComponent", () => {
expect(component.loading.value).toBe(false);
}));

it("should suggest all entities after an initial load", (done) => {
subscription = component.filteredEntities.subscribe((next) => {
expect(next.length).toBe(testUsers.length);
done();
});
it("should suggest all entities after an initial load", fakeAsync(() => {
component.entityType = User.ENTITY_TYPE;
tick();
fixture.detectChanges();
});
expect(component.filteredEntities.length).toBe(testUsers.length);
}));

it("contains the initial selection as entities", fakeAsync(() => {
component.entityType = User.ENTITY_TYPE;
Expand Down Expand Up @@ -154,29 +152,27 @@ describe("EntitySelectComponent", () => {
expect(component.selectedEntities).toBeEmpty();
});

it("autocompletes with the default accessor", (done) => {
it("autocompletes with the default accessor", () => {
component.allEntities = testUsers;
component.loading.next(false);
let iterations = 0;
let expectedLength = 4;
subscription = component.filteredEntities.subscribe((next) => {
iterations++;
expect(next.length).toEqual(expectedLength);
if (iterations === 4) {
done();
}
});
expectedLength = 4;

component.formControl.setValue(null);
expectedLength = 3;
expect(component.filteredEntities.length).toEqual(4);

component.formControl.setValue("A");
expectedLength = 3;
component.formControl.setValue("Ab");
expectedLength = 1;
expect(component.filteredEntities.length).toEqual(3);

component.formControl.setValue("c");
expect(component.filteredEntities.length).toEqual(2);

component.formControl.setValue("Abc");
expect(component.filteredEntities.length).toEqual(1);

component.formControl.setValue("z");
expect(component.filteredEntities.length).toEqual(0);
});

it("should use the configurable toStringAttributes for comparing values", async () => {
it("should use the configurable toStringAttributes for comparing values", fakeAsync(() => {
class Person extends Entity {
static toStringAttributes = ["firstname", "lastname"];

Expand All @@ -189,34 +185,23 @@ describe("EntitySelectComponent", () => {
component.allEntities = [p1, p2];
component.loading.next(false);

let res = firstValueFrom(component.filteredEntities);
component.formControl.setValue("Aa");
await expectAsync(res).toBeResolvedTo([p1, p2]);
tick();
expect(component.filteredEntities).toEqual([p1, p2]);

res = firstValueFrom(component.filteredEntities);
component.formControl.setValue("Aa b");
await expectAsync(res).toBeResolvedTo([p1]);
});
tick();
expect(component.filteredEntities).toEqual([p1]);
}));

it("should add an unselected entity to the filtered entities array", (done) => {
// TODO this is still throwing object unsubscribe error
it("should add an unselected entity to the filtered entities array", () => {
component.allEntities = testUsers;
const selectedUser = testUsers[1];
let iteration = 0;

subscription = component.filteredEntities.subscribe(
(autocompleteEntities) => {
iteration++;
if (iteration === 1) {
expect(autocompleteEntities).not.toContain(selectedUser);
} else if (iteration === 2) {
expect(autocompleteEntities).toContain(selectedUser);
done();
}
}
);

component.selectEntity(selectedUser);
expect(component.filteredEntities).not.toContain(selectedUser);

component.unselectEntity(selectedUser);
expect(component.filteredEntities).toContain(selectedUser);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "@angular/core";
import { COMMA, ENTER } from "@angular/cdk/keycodes";
import { Entity } from "../../../entity/model/entity";
import { BehaviorSubject, Observable } from "rxjs";
import { BehaviorSubject } from "rxjs";
import { FormControl } from "@angular/forms";
import { filter, map } from "rxjs/operators";
import { MatChipInputEvent } from "@angular/material/chips";
Expand Down Expand Up @@ -126,18 +126,22 @@ export class EntitySelectComponent<E extends Entity> implements OnChanges {
inputPlaceholder = this.loadingPlaceholder;

allEntities: E[] = [];
filteredEntities: Observable<E[]>;
filteredEntities: E[] = [];
formControl = new FormControl("");

@ViewChild("inputField") inputField: ElementRef<HTMLInputElement>;
@ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger;

constructor(private entityMapperService: EntityMapperService) {
this.filteredEntities = this.formControl.valueChanges.pipe(
untilDestroyed(this),
filter((value) => value === null || typeof value === "string"), // sometimes produces entities
map((searchText?: string) => this.filter(searchText))
);
this.formControl.valueChanges
.pipe(
untilDestroyed(this),
filter((value) => value === null || typeof value === "string"), // sometimes produces entities
map((searchText?: string) => this.filter(searchText))
)
.subscribe((value) => {
this.filteredEntities = value;
});
this.loading.pipe(untilDestroyed(this)).subscribe((isLoading) => {
this.inputPlaceholder = isLoading
? this.loadingPlaceholder
Expand Down

0 comments on commit 7077698

Please sign in to comment.