5

I am allowing users to create input fields dynamically. For each of these input fields i want to connect it to a different mat-autocomplete so that they work independently of each other. I have hit a brick wall here because I cannot create element reference(#auto here) dynamically that connects the auto-complete to input. How do I achieve this?

<div
  class="row"
  *ngFor="let field of getControls('requestFields'); let i = index"
  formArrayName="requestFields"
>
  <ng-container [formGroupName]="i">
    <div class="col-md-4">
      <mat-form-field class="example-full-width">
        <input
          type="text"
          placeholder="Name"
          matInput
          formControlName="reqName"
          matAutocomplete="auto"
        />
        <mat-autocomplete #auto="matAutocomplete">
          <mat-option
            *ngFor="let option of (filteredColumns | async)"
            [value]="option"
          >
            {{ option }}
          </mat-option>
        </mat-autocomplete>
      </mat-form-field>
    </div>
    <div class="col-md-2">
      <div class="togglebutton">
        <label>
          <span>Optional</span>
          <input type="checkbox" formControlName="reqOption" />
          <span class="toggle"></span>
        </label>
      </div>
    </div>
    <div class="col-md-4">
      <mat-form-field>
        <input
          matInput
          formControlName="reqValidations"
          placeholder="Validation"
          type="text"
        />
      </mat-form-field>
    </div>
  </ng-container>
</div>
Oleksii Aza
  • 5,368
  • 28
  • 35
Vipul Deora
  • 61
  • 1
  • 3
  • for this you can update autocomplete array with user's input value – Kaushik Andani Aug 02 '18 at 08:50
  • Function calls in angular expression will slow down your app, it's a very bad practice, so try to don't call getControls('requestFields') in the ngFor. and try to bind to some users interaction, a click or a key event, so you will call the function which will fabric the html and appends it – Sid Ali Aug 02 '18 at 09:16

1 Answers1

3

A nice thing about mat-autocomplete is that it is completely decoupled from the mat-form-field therefore you can put it at any place outside the scope of dynamically-generated rows. So taking your example - the solution could look like this:

<div
  class="row"
  *ngFor="let field of getControls('requestFields'); let i = index"
  formArrayName="requestFields"
>
  <ng-container [formGroupName]="i">
    <div class="col-md-4">
      <mat-form-field class="example-full-width">
        <input
          type="text"
          placeholder="Name"
          matInput
          formControlName="reqName"
          matAutocomplete="auto"
        />
      </mat-form-field>
    </div>
   <!-- other dynamic content -->
  </ng-container>
</div>
<mat-autocomplete #auto="matAutocomplete">
  <mat-option *ngFor="let option of filteredColumns | async" [value]="option">
    {{ option }}
  </mat-option>
</mat-autocomplete>

Then you could have an event handler for keyup on the input that triggers update for filteredColumns

<mat-form-field class="example-full-width">
  <input
    type="text"
    placeholder="Name"
    matInput
    formControlName="reqName"
    matAutocomplete="auto"
    (keyup)="reqNameChanged(field.get('reqName')?.value)"
  />
</mat-form-field>

And in your component you can set up an filteredColumns observable that gets triggered by a subject from the keyup event handler:

import { Component, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
  debounceTime,
  distinctUntilChanged,
  filter,
  switchMap
} from 'rxjs/operators';

@Component({
  selector: 'example',
  templateUrl: './example.html',
  styleUrls: ['./example.scss']
})
export class ExampleComponent implements OnInit {
  filteredColumns: Observable<string[]>;

  reqNameSubject: Subject<string> = new Subject<string>();

  constructor(private lookup: ILookupService) {}

  ngOnInit() {
    this.filteredColumns = this.reqNameSubject.pipe(
      filter(v => !!v),
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(value =>
        /* call for the autocomplete data */
        this.lookup.search(value)
      )
    );
  }

  reqNameChanged(value: string) {
    this.reqNameSubject.next(value);
  }
}

I hope that it helps.

Oleksii Aza
  • 5,368
  • 28
  • 35