¿El autocompletado de tapete angular no funciona en FormArray?
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
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>
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>