Search code examples
angularviewchildmat-autocomplete

Refer to autocomplete inputs generated dynamically with ngFor loop


I'm setting an invoice Angular page where lines are added dynamically, every line should have an own autocomplete input with other different inputs.How can i refer to each autocomplete input independently inside my component so i can use the right one inside my displayWith function ?

My question is similar to this one but i can't find the right answer there :(

HTML

<tr *ngFor="let ligneFacture of facture.lignesFacture; let i =index;">
    <td width="5%" align="center">
        <button type="button" class="button btn-primary"
                style="border-radius: 50px;" disabled><span>{{i+1}}</span>
        </button>
    </td>
    <td width="25%">
        <form>
            <mat-form-field>
                <input type="text" class="form-control" matInput
                       (ngModelChange)="watchChangesArticle($event)"
                       (focus)="cursorFocusArticle($event)"
                       [matAutocomplete]="autoArticle"
                       [ngModelOptions]="{standalone: true}"
                       [(ngModel)]="ligneFacture.articleId">
                <mat-autocomplete #autoArticle="matAutocomplete"
                                  [displayWith]="displayFnArticle.bind(this)"
                                  [autoActiveFirstOption]="true">
                    <mat-option *ngFor="let article of articles;"
                                [value]="article.id">
                        {{article.libelle}}
                    </mat-option>
                </mat-autocomplete>
            </mat-form-field>
        </form>
    </td>
    <td width="10%"><input type="text" class="form-control"
                           style="text-align: center"
                           [ngModelOptions]="{standalone: true}"
                           [(ngModel)]="ligneFacture.unite"/>
    <td width="10%"><input type="number" class="form-control" #quantity
                           id="quantity{{i+1}}"
                           [ngModelOptions]="{standalone: true}"
                           [(ngModel)]="ligneFacture.quantite"/>
    </td>
    <td width="13%">
        <div class="input-group">
            <input type="number" class="form-control"
                   [ngModelOptions]="{standalone: true}"
                   [(ngModel)]="ligneFacture.tauxTva" readonly/>
            <div class="input-group-append">
                <span class="input-group-text">%</span>
            </div>
        </div>
    </td>

    <td width="15%"><input type="number" class="form-control"
                           [ngModelOptions]="{standalone: true}"
                           [(ngModel)]="ligneFacture.prixUnitaire" tabindex="-1"
                           [value]="updateChanges(ligneFacture)"
                           readonly/></td>

    <td width="15%"><input type="number" class="form-control"
                           [ngModelOptions]="{standalone: true}"
                           [(ngModel)]="ligneFacture.prixTotal" tabindex="-1" readonly/>
    </td>
    <td>
        <div>
            <button type="button" class="btn btn-danger"
                    (click)="removeLine(ligneFacture)" tabindex="-1">
                <span>[X]</span>
            </button>
        </div>
    </td>
</tr>
<br>
<tr>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
    <td colspan="2" align="right">
        <button type="button" class="btn btn-primary" (click)="addLine()">
            <span>[+] Ajouter une ligne</span>
        </button>
    </td>
</tr>

TS:

export class FactureUpdateComponent implements OnInit {
    @ViewChild('autoArticle') matAutocompleteArticle: MatAutocomplete;
    //
    //

    displayFnArticle(item) {
        const matOptions = this.matAutocompleteArticle.options.filter(x => x.value === item);
        if (matOptions.length !== 0) {
            setTimeout(() => {
                const element = document.getElementById('quantity' + this.facture.lignesFacture.length);
                element.focus();
            }, 0);
            return matOptions.map(x => x.viewValue)[0];
        } else {
            return this.facture.lignesFacture.filter(x => x.articleId === item).map(x => x.articleLibelle);
        }
    }

    watchChangesArticle(event, ligneFacture: ILigneFacture) {
        this.articleService.queryByKeyword(event, this.req).subscribe(
            (res: HttpResponse<IArticle[]>) => {
                ligneFacture.articles = res.body;
            },
            (res: HttpErrorResponse) => this.onError(res.message)
        );
    }

    cursorFocusArticle(event, ligneFacture: ILigneFacture) {
        if (event.target.value === '') {
            this.articleService.queryByKeyword('', this.req).subscribe(
                (res: HttpResponse<IArticle[]>) => {
                    ligneFacture.articles = res.body;
                },
                (res: HttpErrorResponse) => this.onError(res.message)
            );
        }
    }
}

Expected result : inside the displayFnArticle function,i can access the autocomplete input of the current line

Actual result : im just getting access to the same first autocomplete input id which may be causing the following dysfunction


Solution

  • You can use template refs there. Docs: https://angular.io/guide/template-syntax#template-reference-variables--var-.

    Example is here: https://stackblitz.com/edit/angular-template-ref-with-fn

    So you have HTML like this:

    <button *ngFor="let i of array" #ref (click)="doStuff(ref)" class="{{ 'a' + i }}">{{ 'a' + i }}</button>
    

    And you handle this like this:

    doStuff(instance: HTMLElement) {
      console.log('>> stuff', instance.classList);
    }
    

    It is just an example with simple elements but you can do same with MatAutocomplete.