¿El autocompletado de tapete angular no funciona en FormArray?

Sep 29 2020

Estoy tratando de implementar el autocompletado de mat en FormArray. Seguí este enlace pero mi consola tiene muchos errores cuando intento abrir el enlace cuando pongo este código como el ejemplo de abajo:

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)),
    );
}

Agradecería que alguien mirara el código y viera lo que me perdí. Gracias.

EDITAR

Después de sus sugerencias @polyglot, algunos errores desaparecieron, solo hay un problema con la tubería asíncrona y ngFor para esta matriz como en la imagen de abajo y hay un error manageNameControl para this.filteredEmployees [index].

El tipo 'Observable <Observable <IEmployee [] >>' no se puede asignar al tipo 'Observable'. Al tipo 'Observable <IEmployee []>' le faltan las siguientes propiedades del tipo 'IEmployee': id, firstName,

Respuestas

polyglot Sep 29 2020 at 20:47

Primero, no inicializó filteredEmployees. Debiera ser:

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

A continuación, usó [formGroupName]="j"dos veces en create-project.html. Eliminar <ng-container [formGroupName]="j">contenedor. Y deberías empezar con [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

RESUELTO

He solucionado el problema filtradoEmyployee debe ser

FilterEmployees: IEmployee [] y el método manageNameControl se parece a

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));
}

y eliminó las tuberías aync con formato html

  <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>