1
votes

I am working on Angular 6. I have a formGroup with a formArray. The fields of formArray can be added dynamically. I need to add Material Autocomplete for a form field of formArray. It's not happening. I followed How to use mat-autocomplete (Angular Material Autocomplete) inside FormArray (Reactive Forms) this. However, it does not work. When value changes in formField, does not trigger the function.

export interface IBrand {
    BrandId: string;
    BrandName: string;
}
//.......................
brandDatas: Observable<IBrand[]>[] = [];
bran: IBrand[] = [
    { "BrandId": "1234", "BrandName": "Apple" },
    { "BrandId": "12344", "BrandName": "Samsung" },
    { "BrandId": "12345", "BrandName": "Oppo" },
    { "BrandId": "12347", "BrandName": "Sony" },
    { "BrandId": "12349", "BrandName": "Panasonic" },

]

this.formGroup = this.fb.group({

    PurchaseDate: new FormControl('', [Validators.required]),
    BillNo: new FormControl('', [Validators.required]),
    TotalCash: new FormControl(''),
    Total: new FormControl(''),
    ProductInformation: this.fb.array([
});

ngOnInit() {
    const productInformation = this.fb.group({
        ProductCategoryId: [''],
        BrandId: [''],
        ProductId: [''],
    })
    this.multipleform.push(productInformation);
    this.totalValue = 0;
    this.ManageNameControl(0);
}

ManageNameControl(index: number) {
    var arrayControl = this.formGroup.get('ProductInformation') as FormArray;
    let item = arrayControl.at(index);

    this.brandDatas[index] = item.get('BrandId').valueChanges
        .pipe(
            startWith(''),
            map(value => this._filterBrand(value))
        );
}

_filterBrand(name: string): IBrand[] {
    return this.bran.filter(opt =>
        opt.BrandName.toLowerCase().includes(name.toLowerCase()));
}

displayBrand(brand?: IBrand): string | undefined {
    return brand ? brand.BrandName : undefined;
}

Inside html

<div formArrayName="ProductInformation">
    <div *ngFor="let productInformation of multipleform.controls; let i=index" [formGroupName]="i">
       ...
        <mat-form-field fxFlex="12%">
          <input [formControl]="BrandCtrl" matInput placeholder="Brand"
                     [matAutocomplete]="auto" formControlName="BrandId" minlength="3" />
            <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayBrand">
               <mat-option *ngFor="let brand of brandDatas[i] | async" [value]="brand">
                  <span>{{brand.BrandName}}</span>
               </mat-option>
             </mat-autocomplete>
          </mat-form-field>
       ...
    </div>
</div>

On debugging, the ManageNameControl function is invoked at first and when the form field is dynamically added and the valueChanges triggers at that time only. After that, valueChanges does not trigger on changing text on Brand field. How can this problem be solved? How can this sort of materail autocomplete be implemented.

2

2 Answers

1
votes

A template reference must be unique in you document. In your case the MatAutocomplete can be reused because it has the same functionality. If you put your MatAutocomplete outside your ngFor loop it should work.

<div formArrayName="ProductInformation">
    <div *ngFor="let productInformation of multipleform.controls; let i=index" [formGroupName]="i">
       ...
        <mat-form-field fxFlex="12%">
          <input [formControl]="BrandCtrl" matInput placeholder="Brand"
                     [matAutocomplete]="auto" formControlName="BrandId" minlength="3" /
          </mat-form-field>
       ...
    </div>
</div>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayBrand">
   <mat-option *ngFor="let brand of brandDatas[i] | async" [value]="brand">
       <span>{{brand.BrandName}}</span>
    </mat-option>
</mat-autocomplete>
0
votes

I was facing similar issue while mat-auto-complete inside for loop.

Move your forloop code to separate component and keep formgroup as input to the component. Then call the new component from ngFor and pass the formgroup.