18
votes

I am using mat-auto complete component from material.angular.io. The default behavior is user can input any value as well as it gives options to choose from. Also you can add your input to chosen values. You can check example here. https://stackblitz.com/angular/ngmvgralayd?file=app%2Fautocomplete-simple- example.html

here is the code I am using for generating auto complete input field.

<form class="example-form">
  <mat-form-field class="example-full-width">
    <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto" disabled="true">
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let option of options" [value]="option">
        {{ option }}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

But I want the form field to take only values from the given option and want to prevent from entering any values by users apart from given option. How to achieve this? It is like select input with auto complete feature.

5
Autocomplete is supposed to let the user type anything he wants. If you want to limit his choices to only the options, consider using a select instead.user4676340
But I want to enable search in for select and that;s why I find auto complete suitable for it. It will give me desired result if I can stop form field to accept user defined values. Can it be stopped by some typescript function?Talk is Cheap Show me Code
It can always be stopped. But that's not how an autocomplete is supposed to work. The user should be free to type whatever he wants, you only provide him with some results.user4676340
You can use primeNg Dropdown and style it according the material design, but it will bloat your applicationDavid Votrubec
@trichetriche This is something I am looking for. But Can we use filter in select options? If yes, Then it will give me more accurate desired result.Talk is Cheap Show me Code

5 Answers

8
votes

Found this solution on github it may provide a simple alternative to those who end up here.

Create a custom validator:

private requireMatch(control: FormControl): ValidationErrors | null {
  const selection: any = control.value;
  if (this.options && this.options.indexOf(selection) < 0) {
    return { requireMatch: true };
  }
  return null;
}

Attach it to your control (we need to bind it to this so that our validator can access our options in our component's scope)

  myControl = new FormControl(undefined, [Validators.required, this.requireMatch.bind(this)]);

Optionally show error:

  <mat-error *ngIf="myControl.errors?.requireMatch">Value need match available options</mat-error>

Example here -----------> https://stackblitz.com/edit/angular-hph5yz

7
votes

You can do something like this

Markup:

<md-input-container class="full-width">
<input mdInput [mdAutocomplete]="autoData"
       #searchMyData
       formControlName="myControl"
       (keyup)="changeMyControl()">
</md-input-container>
<md-autocomplete #autoData="mdAutocomplete">
<md-option
    *ngFor="let option of options"
    [value]="option.name"
    (onSelectionChange)="onSelectedOption($event.source.selected, option.id);">
    {{ option.name }}
</md-option>
</md-autocomplete>

Component:

selectedOption;
changeMyControl(): void {
    if (isUndefined(this.selectedOption) {
        // also check selected item and entered text are not same
        this.myForm.get('myControl').setErrors({'incorrect': true});
    }
}

onSelectedOption(isSelected: boolean, id: number): void {
    if (isSelected) {
        setTimeout(() => {
            const option = this.options.filter(bt => bt.id === id);
            if (option.length > 0) {
                this.selectedOption= option[0];
               // patch formcontrol value here
            }
        }, 200);
    }
}
1
votes

As already suggested in comment by @trichetriche this is a use case for select.

You can use material version of select, like this

<mat-form-field>
  <mat-select placeholder="Favorite food">
    <mat-option *ngFor="let food of foods" [value]="food.value">
      {{ food.viewValue }}
    </mat-option>
  </mat-select>
</mat-form-field>

If you need filter above the select, than I suggest to you PrimeNg Dropdown https://www.primefaces.org/primeng/#/dropdown

-1
votes

The Material demo for chips autocomplete shows bindings on both the input and to the mat-autocomplete:

<input (matChipInputTokenEnd)="add($event)">
<mat-autocomplete (optionSelected)="selected($event)"></mat-autocomplete>

If you only want to allow options from the autocomplete, just omit the add function from the input.

-1
votes

I think there is a UI/UX question here - in what way do we prevent the user from typing something that is not in the list of options, but still allow them to filter by a string?

I see a couple of potential options. First one is to just display an error "Invalid entry" when the option isn't in the list adjacent to the input. The second option would be to actually prevent the entry of characters that no longer match any options. So if there is a single option "foo" and a user types "for", only "fo" would be accepted, and the 'r' gets thrown out.

The PrimeNg solution is not quite the same as a text field that allows a user to start typing on focus. The user needs to first click to open a search, and there appears to be no keyboard accessibility. I don't really see why they haven't implemented it such that display and the search are the same, except they've got logos displayed.