Angular Mat Autocomplete funktioniert nicht in FormArray?

Sep 29 2020

Ich versuche, die automatische Vervollständigung von Matten in FormArray zu implementieren. Ich bin diesem Link gefolgt, aber meine Konsole hat viele Fehler, wenn ich versuche, den Link zu öffnen, wenn ich diesen Code wie im folgenden Beispiel eingebe:

https://stackblitz.com/edit/angular-szxkme-yfphur?file=app%2Fautocomplete-display-example.ts

create-project.ts

export class CreateProjectComponent implements OnInit {
pageNavigation: IPageNavigation = {
    pageTitle: 'Project Management',
    subPages: [{ label: 'Projects', link: '/projects' }],
};

createProjectForm: FormGroup;
projectTeamMemberFormArray = new FormArray([]);      
filteredEmployees: Observable<IEmployee[]>;

constructor(       
    private formBuilder: FormBuilder,
    private projectManagementService: ProjectManagementService       
) { }

ngOnInit(): void {       
    this.buildForm();
}

buildForm(): void {
    this.createProjectForm = this.formBuilder.group({            
        projectTeamMembers: this.projectTeamMemberFormArray
    });
}

manageNameControl(index: number) {
    var arrayControl = this.createProjectForm.get('projectTeamMembers') as FormArray;
    this.filteredEmployees[index] = arrayControl.at(index).get('firstName').valueChanges
        .pipe(
            startWith<string | IEmployee>(''),
            map(value => typeof value === 'string' ? value : value.firstName),
            map(name => name ? this.projectManagementService.filterEmployees(name) : null)
        );
}

addProjectTeamMember(teamMember?: IProjectMember): void {
    this.projectTeamMemberFormArray = this.createProjectForm.get('projectTeamMembers') as FormArray;
    this.projectTeamMemberFormArray.push(this.createProjectTeamMemberItem(teamMember));

    this.manageNameControl(this.projectTeamMemberFormArray.length - 1);
}

createProjectTeamMemberItem(teamMember?: IProjectMember): FormGroup {
    return this.formBuilder.group({
        employeeId: new FormControl((teamMember && teamMember.employeeId) || null, Validators.required),
        firstName: new FormControl((teamMember && teamMember.firstName) || null, Validators.required),
        memberRoles: new FormControl((teamMember && teamMember.projectRoles) || null, Validators.required),
    });
}

removeProjectTeamMemberItem(index: number, item: IProjectMember) {
    this.projectTeamMemberFormArray = this.createProjectForm.get('projectTeamMembers') as FormArray;
    this.projectTeamMemberFormArray.removeAt(index);
    this.projectTeamMembers.forEach((c: IProjectMember) => {
        if (c.id === item.id) c.deleted = true;
    });
  }
selectEmployee(employee: IEmployee): void {
    this.createProjectForm.get('employeeId').setValue(employee.id);
}

employeeTypeaheadDisplayFn(employee: IEmployee): string {
    if (employee) {
        return `${employee.lastName} ${employee.firstName}`;
    }
    return '';
}
}

create-project.html

  <form #f="ngForm" (ngSubmit)="submit()" novalidate  [formGroup]="createProjectForm">
    <section>
        <div formArrayName="projectTeamMembers" class="single-item-container"
            *ngFor="let t of createProjectForm.get('projectTeamMembers').controls; let j = index">
            <ng-container [formGroupName]="j">
            <div class="spinner" *ngIf="showLoader">
                <mat-progress-spinner [color]="'primary'" diameter="100" mode="indeterminate" value="50">
                </mat-progress-spinner>
            </div>
            <div [formGroupName]="j" *ngIf="!showLoader" fxLayout="row" fxLayoutGap="2%">
                <!-- employee -->
                <mat-form-field fxFlex="100%">
                    <input matInput formControlName='firstName' placeholder="Start typing employee first or last name."
                        [matAutocomplete]="auto">
                    <mat-error *ngIf="t.get('firstName').errors?.required">
                        {{ 'FIELD_REQUIRED_MESSAGE' | translate }}
                    </mat-error>
                </mat-form-field>
                <mat-autocomplete #auto="matAutocomplete" [displayWith]="employeeTypeaheadDisplayFn" (optionSelected)="selectEmployee($event.option.value.id)">
                    <mat-option *ngFor="let employee of filteredEmployees[j] | async" [value]="employee">
                        {{ employee.firstName }} {{ employee.lastName }}
                    </mat-option>
                </mat-autocomplete>
                <div class="input-bottom">
                    <button mat-raised-button color="warn" (click)="removeProjectTeamMemberItem(j, t.value)">
                        Delete
                    </button>
                </div>
            </div>
        </ng-container>
        </div>
        <div class="form-footer">
            <div class="items-buttons">
                <button mat-raised-button color="primary" type="button"
                    (click)="addProjectTeamMember()">{{'COMPONENTS.PROJECT.ADD_NEW_TEAM_MEMBER' | translate}}</button>
            </div>
        </div>
    </section>
</form>

project-management.service.ts

export interface IEmployee {
id: number;
firstName: string;
lastName: string;
}
   filterEmployees(searchTerm: string): Observable<IEmployee[]> {
    const reqUrl = `${environment.mPortalWebApi}employee-profiles/search?searchTerm=${searchTerm}`;

    return this.http.get(reqUrl).pipe(
        map((response: IEmployee[]) => response),
        catchError((error) => _throw(error)),
    );
}

Ich würde es begrüßen, wenn jemand den Code anschauen und sehen würde, was ich vermisst habe. Vielen Dank.

BEARBEITEN

Nach Ihren Vorschlägen @polyglot sind einige Fehler verschwunden, nur dort gibt es ein Problem mit der asynchronen Pipe und ngFor für dieses Array, wie in der folgenden Abbildung dargestellt, und es gibt einen Fehler bei manageNameControl für this.filteredEmployees [index].

Der Typ 'Observable <Observable <IEmployee [] >>' kann nicht dem Typ 'Observable' zugewiesen werden. Beim Typ 'Observable <IEmployee []>' fehlen die folgenden Eigenschaften vom Typ 'IEmployee': id, firstName,

Antworten

polyglot Sep 29 2020 at 20:47

Zuerst haben Sie nicht initialisiert filteredEmployees. Sollte sein:

filteredEmployees: Observable<IEmployee>[] = []; // Not Observable<IEmployee[]>

Als Nächstes haben Sie [formGroupName]="j"zweimal in create-project.html verwendet. Delete - <ng-container [formGroupName]="j">Wrapper. Und du solltest damit beginnen [formGroup]="createProjectForm".

<section [formGroup]="createProjectForm">
    <div formArrayName="projectTeamMembers" class="single-item-container"
        *ngFor="let t of createProjectForm.get('projectTeamMembers').controls; let j = index">
        <div class="spinner" *ngIf="showLoader">
            <mat-progress-spinner [color]="'primary'" diameter="100" mode="indeterminate" value="50">
            </mat-progress-spinner>
        </div>
        <div [formGroupName]="j" *ngIf="!showLoader" fxLayout="row" fxLayoutGap="2%">
            <!-- employee -->
            <mat-form-field fxFlex="100%">
                <input matInput formControlName='firstName' placeholder="Start typing employee first or last name."
                    [matAutocomplete]="auto">
                <mat-error *ngIf="t.get('firstName').errors?.required">
                    {{ 'FIELD_REQUIRED_MESSAGE' | translate }}
                </mat-error>
            </mat-form-field>
            <mat-autocomplete #auto="matAutocomplete" [displayWith]="employeeTypeaheadDisplayFn" (optionSelected)="selectEmployee($event.option.value.id)">
                <mat-option *ngFor="let employee of filteredEmployees[j] | async" [value]="employee">
                    {{ employee.firstName }} {{ employee.lastName }}
                </mat-option>
            </mat-autocomplete>
            <div class="input-bottom">
                <button mat-raised-button color="warn" (click)="removeProjectTeamMemberItem(j, t.value)">
                    Delete
                </button>
            </div>
        </div>
    </div>
    <div class="form-footer">
        <div class="items-buttons">
            <button mat-raised-button color="primary" type="button"
                (click)="addProjectTeamMember()">{{'COMPONENTS.PROJECT.ADD_NEW_TEAM_MEMBER' | translate}}</button>
        </div>
    </div>
</section>
redux17 Sep 30 2020 at 10:32

BEHOBEN

Ich habe das Problem behoben, das gefiltert sein muss

filteredEmployees: IEmployee [] und die Methode manageNameControl sehen aus

manageNameControl(index: number) {
    var arrayControl = this.createProjectForm.get('projectTeamMembers') as FormArray;
    arrayControl.at(index).get('employeeId').valueChanges
        .pipe(
            debounceTime(300),
            switchMap((value) => this.projectManagementService.filterEmployees(value)),
        )
        .subscribe((employees: IEmployee[]) => (this.filteredEmployees = employees));
}

und aync Pipes mit aus HTML-Form entfernt

  <mat-form-field fxFlex="100%">
                    <input matInput formControlName='employeeId' name='employeeId' placeholder="Start typing employee first or last name."
                        [matAutocomplete]="auto">
                    <mat-error *ngIf="t.get('employeeId').errors?.required">
                        {{ 'FIELD_REQUIRED_MESSAGE' | translate }}
                    </mat-error>
                </mat-form-field>
                <mat-autocomplete #auto="matAutocomplete"  [displayWith]="employeeTypeaheadDisplayFn(filteredEmployees)">
                    <mat-option *ngFor="let employee of filteredEmployees" [value]="employee.id">
                        {{ employee.firstName }} {{ employee.lastName }}
                    </mat-option>
                </mat-autocomplete>