Topic: Example of two autocompleters on the same page

Jordan free asked 5 years ago


I am having trouble getting 2 auto-completers to work independently on the same form. I used separate methods and variables for them both but when I select an option for the first autocomplete, that selection seems to be used as filter criteria that is applied to the second auto complete. Should I have two instances of " @ViewChild(MdbAutoCompleterComponent) completer: MdbAutoCompleterComponent;"?

Also, I am having issue getting the selected value committed to the form model. For example:

I would type "Acco" and then select the "Accounting" option. However, when the form is saved, only the value ("Acco" ) I explicitly typed is saved.

TS:

import { Component, ViewChild, AfterViewInit, Inject, LOCALE_ID } from "@angular/core";
import { formatDate } from "@angular/common";
import { ListItem, DataGroup, Client } from "../../api-client/app.generated";
import { MdbTableEditorDirective } from "mdb-table-editor";
import { MdbAutoCompleterComponent } from "ng-uikit-pro-standard"
import {
  FormGroup, FormControl, Validators
  //, ReactiveFormsModule
} from "@angular/forms";
import { of } from "rxjs";


@Component({
  selector: "app-data-groups-component",
  templateUrl: "./data-groups.component.html"
})

export class DataGroupsComponent implements AfterViewInit {
  @ViewChild("table") mdbTableEditor: MdbTableEditorDirective;
  @ViewChild(MdbAutoCompleterComponent) completer: MdbAutoCompleterComponent;


  public availableAmsRoles = [""];

  public searchAmsRolesTextForMember = "";
  public amsRolesResultsForMember: any;
  public searchAmsRolesTextForAdmin = "";
  public amsRolesResultsForAdmin: any;



  constructor(private apiClient: Client
    , @Inject(LOCALE_ID) private locale: string) {
    this.dataGroupForm = this.createForm();

    this.apiClient.amsUserRoles_GetAmsUserRoles()
    .toPromise()
      .then((result: string[]) => {
        this.availableAmsRoles = result;
      });

    this.amsRolesResultsForMember = this.searchAmsRolesForMember(this.searchAmsRolesTextForMember);
    this.amsRolesResultsForAdmin = this.searchAmsRolesForAdmin(this.searchAmsRolesTextForAdmin);
  }


  public ngAfterViewInit() {
    this.completer.selectedItemChanged().subscribe((data: any) => {
      this.searchAmsRolesTextForMember = data.text;
      this.getFilteredAmsRolesForMember();

      this.searchAmsRolesTextForAdmin = data.text;
      this.getFilteredAmsRolesForAdmin();
    });

  }

  public searchAmsRolesForAdmin(term: string) {
    return of(this.availableAmsRoles.filter((data: any) => data.toString().toLowerCase().includes(term.toString().toLowerCase())));
  }

  public searchAmsRolesForMember(term: string) {
    return of(this.availableAmsRoles.filter((data: any) => data.toString().toLowerCase().includes(term.toString().toLowerCase())));
  }

  public getFilteredAmsRolesForMember() {
    this.amsRolesResultsForMember = this.searchAmsRolesForMember(this.searchAmsRolesTextForMember);
  }
  public getFilteredAmsRolesForAdmin() {
    this.amsRolesResultsForAdmin = this.searchAmsRolesForAdmin(this.searchAmsRolesTextForAdmin);
  }

  public onMemberChange() {
    this.getFilteredAmsRolesForMember();
  }

  public onAdminChange() {
    this.getFilteredAmsRolesForAdmin();
  }


  public onAdminSelect(role: string) {
    console.log("onAdminSelect");
    console.log(role);


    this.dataGroupForm.controls["amsAdminRole"].patchValue(role);
    this.dataGroupForm.controls["amsAdminRole"].updateValueAndValidity();
  }

  public onMemberSelect(role: string) {
    console.log("onMemberSelect");
    console.log(role);

    this.dataGroupForm.controls["amsMemberRole"].patchValue(role);
    this.dataGroupForm.controls["amsMemberRole"].updateValueAndValidity();
  }


 protected createForm(): FormGroup {
    return new FormGroup({

      amsAdminRole: new FormControl(null, Validators.required),
      amsMemberRole: new FormControl(null, Validators.required),

    });
  }

}

HTML:

        <div class="md-form input-group mb-5">
          <input mdbValidate
                 formControlName="amsAdminRole"
                 type="text"
                 class="completer-input form-control mdb-autocomplete mb-0"
                 (keydown)="searchAmsRolesTextForAdmin = $event.target.value"
                 (input)="getFilteredAmsRolesForAdmin()"
                 (ngModelChange)="onAdminChange()"
                 [mdbAutoCompleter]="autoAdmin"
                 placeholder="Enter the AMS function for admin access">
          <mdb-auto-completer #autoAdmin="mdbAutoCompleter" textNoResults="No roles match your criteria...">
            <mdb-option *ngFor="let option of amsRolesResultsForAdmin | async"
                        [value]="option"
                        (select)="onAdminSelect(option)">
              {{option}}
            </mdb-option>
          </mdb-auto-completer>
          <mdb-error *ngIf="amsAdminRole.invalid && (amsAdminRole.dirty || amsAdminRole.touched)">
          Please specify an AMS role
          </mdb-error>
        </div>




        <div class="md-form input-group mb-5">
          <input mdbValidate
                 formControlName="amsMemberRole"
                 type="text"
                 class="completer-input form-control mdb-autocomplete mb-0"
                 (keydown)="searchAmsRolesTextForMember = $event.target.value"
                 (input)="getFilteredAmsRolesForMember()"
                 (ngModelChange)="onMemberChange()"
                 [mdbAutoCompleter]="autoMember"
                 placeholder="Enter the AMS function for member access">
          <mdb-auto-completer #autoMember="mdbAutoCompleter" textNoResults="No roles match your criteria...">
            <mdb-option *ngFor="let option of amsRolesResultsForMember | async"
                        [value]="option"
                        (select)="onMemberSelect(option)">
              {{option}}
            </mdb-option>
          </mdb-auto-completer>

          <mdb-error *ngIf="amsMemberRole.invalid && (amsMemberRole.dirty || amsMemberRole.touched)">
            Please specify an AMS role
          </mdb-error>


        </div>
      </form>

Damian Gemza staff answered 5 years ago


Dear @Jordan

Please take a look at the below code. There's separated methods and fields for every mdb-auto-completer component. Also, I have changed the @ViewChild to target by template reference (#).

.html:

<div class="container">
  <div class="row">
    <div class="col-md-6 mx-auto my-5">
      <form>
        <div class="md-form">
          <input type="text" class="completer-input form-control mdb-autocomplete"
                 [(ngModel)]="searchText"
                 name="completer1"
                 (input)="getFilteredData()" (ngModelChange)="onChange()"
                 [mdbAutoCompleter]="auto"
                 placeholder="Choose your color">
          <mdb-auto-completer #completer1 #auto="mdbAutoCompleter" textNoResults="I have found no results :(">
            <mdb-option *ngFor="let option of results | async" [value]="option">
              {{option}}
            </mdb-option>
          </mdb-auto-completer>
        </div>

        <div class="md-form">
          <input type="text" class="completer-input form-control mdb-autocomplete"
                 [(ngModel)]="searchText2"
                 name="completer2"
                 (input)="getFilteredData2()" (ngModelChange)="onChange2()"
                 [mdbAutoCompleter]="auto2"
                 placeholder="Choose your number">
          <mdb-auto-completer #completer2 #auto2="mdbAutoCompleter" textNoResults="I have found no results :(">
            <mdb-option *ngFor="let option of results2 | async" [value]="option">
              {{option}}
            </mdb-option>
          </mdb-auto-completer>
        </div>
      </form>
    </div>
  </div>
</div>

ts:

import {Component, ViewChild} from '@angular/core';
import {of} from "rxjs";
import {MdbAutoCompleterComponent} from "ng-uikit-pro-standard";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @ViewChild('completer1') completer: MdbAutoCompleterComponent;
  @ViewChild('completer2') completer2: MdbAutoCompleterComponent;

  searchText = '';
  searchText2 = '';

  results: any;
  results2: any;

  data: any = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'black'];
  data2: any = [1, 2, 3, 4, 5, 6, 7];

  constructor() {
    this.results = this.searchEntries(this.searchText);
    this.results2 = this.searchEntries2(this.searchText2);
  }

  getDataItems() {
    return this.data;
  }

  getDataItems2() {
    return this.data2;
  }

  searchEntries(term: string) {
    return of(this.getDataItems().filter((data: any) => data.toString().toLowerCase().includes(term.toString().toLowerCase())));
  }

  searchEntries2(term: string) {
    return of(this.getDataItems2().filter((data: any) => data.toString().toLowerCase().includes(term.toString().toLowerCase())));
  }

  getFilteredData() {
    this.results = this.searchEntries(this.searchText);
  }

  getFilteredData2() {
    this.results2 = this.searchEntries2(this.searchText2);
  }

  onChange() {
    this.getFilteredData();
  }

  onChange2() {
    this.getFilteredData2();
  }

  ngAfterViewInit() {
    this.completer.selectedItemChanged().subscribe((data: any) => {
      this.searchText = data.text;
      this.getFilteredData();
    });

    this.completer2.selectedItemChanged().subscribe((data: any) => {
      this.searchText2 = data.text;
      this.getFilteredData2();
    });
  }
}

Best Regards,

Damian


Jordan free commented 5 years ago

Perfect. That was what I was looking for.

Also note that in order for the selected value to recognized/valid, I had to add the patchvalue method for the control.

this.memberCompleter.selectedItemChanged().subscribe((memberCompleterData: any) => {

  console.log("onMemberSelect");
  console.log(memberCompleterData);

  this.searchAmsRolesTextForMember = memberCompleterData.text;
  this.getFilteredAmsRolesForMember();

  this.dataGroupForm.controls['amsMemberRole'].patchValue(memberCompleterData.text);
  this.dataGroupForm.controls['amsMemberRole'].updateValueAndValidity();
});

Damian Gemza staff answered 5 years ago


Dear @Jordan

That's quite easy. In the @ViewChild() we're not targetting the mdb-auto-completer by template reference (# sign), but by class name (MdbAutoCompleterComponent).

With this approach, we're able to use the instance of the mdb-auto-completer component without using template reference.

If something isn't clear for you, feel free to ask!

Best Regards,

Damian


Jordan free commented 5 years ago

But that still doesn't give me an understanding of what I need to do.Do I need one or two instances of @ViewChild(MdbAutoCompleterComponent) completer: MdbAutoCompleterComponent;?How will the subscription to the completer components work?Can you send me code example?


Jordan free commented 5 years ago

Apologies... but I have limited experience with Angular.


Arkadiusz Idzikowski staff answered 5 years ago


All variables should be unique for every mdb-autocompleter component. Please try to use different template reference variables and ViewChild declarations or wrap the autocompleters in your custom components so they don't have access to the same variables.


Jordan free commented 5 years ago

I dont understand how the completer declared in the TS is bound to the html component. Your example (https://mdbootstrap.com/docs/angular/forms/autocomplete/#preselect-reactive) uses: this.completer. But that is not referenced in the HTML (at least by name).



Please insert min. 20 characters.

FREE CONSULTATION

Hire our experts to build a dedicated project. We'll analyze your business requirements, for free.

Status

Resolved

Specification of the issue

  • ForumUser: Free
  • Premium support: No
  • Technology: MDB Angular
  • MDB Version: 7.5.0
  • Device: PC
  • Browser: Chrome
  • OS: Windows 10
  • Provided sample code: No
  • Provided link: No