O preenchimento automático de tapete angular não funciona no FormArray?

Sep 29 2020

Estou tentando implementar o preenchimento automático de esteira no FormArray. Segui este link, mas meu console apresenta muitos erros quando tento abrir o link ao colocar este código como o exemplo abaixo:

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

Agradeceria se alguém olhasse o código e visse o que perdi. Obrigado.

EDITAR

Depois de suas sugestões @polyglot, alguns erros desapareceram apenas lá o problema com o pipe assíncrono e ngFor para este array como na imagem abaixo e há o erro manageNameControl para this.filteredEmployees [index].

O tipo 'Observable <Observable <IEmployee [] >>' não pode ser atribuído ao tipo 'Observable'. O tipo 'Observable <IEmployee []>' não contém as seguintes propriedades do tipo 'IEmployee': id, firstName,

Respostas

polyglot Sep 29 2020 at 20:47

Primeiro, você não inicializou filteredEmployees. Deveria estar:

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

Em seguida, você usou [formGroupName]="j"duas vezes em create-project.html. Exclua o <ng-container [formGroupName]="j">wrapper. E você deve começar [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

RESOLVIDO

Corrigi o problema filtrado; meu funcionário precisa ser

filterEmployees: IEmployee [] e o método manageNameControl parece

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

e removeu os tubos aync com do formulário 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>