components icon indicating copy to clipboard operation
components copied to clipboard

Input's FormControl setValue does not work when mat-form-field has chip-list and input is linked to autocomplete

Open ffredsh opened this issue 6 years ago • 17 comments

Bug, feature request, or proposal:

Bug

What is the expected behavior?

Input's FormControl's setValue should work for an input that is inside a mat-form-field that has both autocomplete and chip-list.

What is the current behavior?

FormControl setValue does not work when the following iff a mat-form-field has an input that has matAutocomplete AND the mat-form-field has a mat-chip-list.

SetValue works if one removes the matAutocomplete directive from the input or removes the mat-chip-list element.

What are the steps to reproduce?

Demo should have the input incrementing a number every second, which doesn't work:

https://stackblitz.com/edit/angular-ay3yts?file=app%2Fautocomplete-simple-example.html

Deleting mat-chip-list or removing the matAutocomplete directive fixes the issue.

What is the use-case or motivation for changing an existing behavior?

I want to be able to programmatically add a chip then clear the input when an option in the autocomplete has been selected.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular: 5.2.4 Material: 5.2.4

Is there anything else we should know?

Nope :)

ffredsh avatar Apr 24 '18 01:04 ffredsh

The stackblitz does not appear to be set up correctly. The <input> needs a matChipInputFor directive rather than a matInput directive (see: https://material.angular.io/components/chips/overview). I also don't think setting a formControl on the input will add chips to the chipList

@tinayuangao is there a way to put a formControl on the mat-chip-list itself so they can update the chips by changing the formControl value?

mmalerba avatar Apr 24 '18 22:04 mmalerba

Oops, how embarrassing! I had to recreate the stackblitz after accidentally navigating away from the first one. I've updated the stackblitz.

I'm not looking to add chips through form control, I just want to be able to have the input respond to FormControl's setValue calls. Right now, if you have both a mat-chip-list AND autocomplete, the input view will not respond to setValue. Try commenting out the mat-chip-list OR the autocomplete, you'll see that the input will once again respond to setValue and show a counter going up.

ffredsh avatar Apr 25 '18 02:04 ffredsh

Hmm interesting. I made an expanded example that shows this working with other combinations of input and autocomplete but failing for chips + autocomplete. It must be some kind of bad interaction between the two components.

@crisbeto as well, as owner of autocomplete

mmalerba avatar Apr 25 '18 18:04 mmalerba

I'm doing same, chips + autocomplete + Input. The purpose of a combination is allowing a user to select multiple options with searching using APIs (remote data).

Searching will populate autocomplete options and after selecting autocomplete option, I'm populating chip list selected items.

After selection of autocomplete item, value clearance is not working.

<mat-form-field class="demo-chip-list">
	<mat-chip-list #tolist>
		<mat-chip *ngFor="let user of selectedUser" [selectable]="true" [removable]="true">
			{{user.name}}
			<mat-icon matChipRemove>cancel</mat-icon>
		</mat-chip>
		<input matInput placeholder="New user..." type="text" 
		name="to" formControlName="to"
		[matChipInputFor]="tolist"
		(matChipInputTokenEnd)="add($event)" />
	</mat-chip-list>
	<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
		<mat-option *ngFor="let user of users" [value]="user">
			{{ user.name }}
		</mat-option>
	</mat-autocomplete>
</mat-form-field>
selected(event){
    this.selectedUser.push(event.option.value);
    this.composeForm.controls['to'].setValue('abc');
}

bhaumikpandhi avatar Jun 12 '18 16:06 bhaumikpandhi

Hi,

I have the same issue. This causes the following behavior when using the chips with autocomplete example (https://stackblitz.com/angular/beoyjxqloag?file=app%2Fchips-autocomplete-example.ts):

  1. Type "L"
  2. Select a suggestion (ex: "Lemon")
  3. Type "L" again
  4. The suggestions are not shown. I guess it's because the valueChanges was not triggered (the field value is still "L")

Thanks, Marc

marcantoinebouchard avatar Jun 20 '18 20:06 marcantoinebouchard

As a temporary fox for this you can do:

  <mat-form-field *ngIf="formGroup.get('categories') as control">
    <mat-chip-list #categoriesChipList>
      <mat-chip *ngFor="let selected of control.controls; let i = index;" removable="true"
        (removed)="removeCategory(i)">
...
      <input #categoryInput matInput formControlName="categoryInput"
...
    <mat-hint *ngIf="formGroup.get('categoryInput').touched && control.invalid; else showLabel">
      <mat-error>At least 1 category is required</mat-error>
    </mat-hint>
    <ng-template #showLabel>
      <mat-hint>Categories</mat-hint>
    </ng-template>

This will show the error over the hint at least. The control is not underlined in red though..

ausmurp avatar Oct 26 '18 02:10 ausmurp

Having the same issue here. Combining autocomplete and chips on a formField and using control.setValue(...) won't do anything.

Interestingly enough, valueChanges got triggered with the value that is used in control.setValue(...) (but is not displayed)

njifares avatar Jun 11 '19 13:06 njifares

Having the same issue here. Combining autocomplete and chips on a formField and using control.setValue(...) won't do anything.

Interestingly enough, valueChanges got triggered with the value that is used in control.setValue(...) (but is not displayed)

Check your displayFn, if not send more code maybe I can help, I've been searching for bug for hours and finally found it maybe I can help you too

nikagar4 avatar Jun 14 '19 14:06 nikagar4

Same issue here. Any solution?

The only workaround I found is to set the value manually:

<mat-form-field>
  <mat-chip-list #chipList>
    <mat-chip *ngFor="let value of selected">{{value}}</mat-chip>
    <input #inputRef
           placeholder="Input Name"
           [formControl]="inputFormControl"
           [matAutocomplete]="autocomplete"
           [matChipInputFor]="chipList"
           (matChipInputTokenEnd)="add($event.value)">
  </mat-chip-list>
  <mat-autocomplete #autocomplete (optionSelected)="add($event.option.viewValue)">
    <mat-option *ngFor="let value of values" value="{{value}}">{{value}}</mat-option>
  </mat-autocomplete>
</mat-form-field>
  inputFormControl = new FormControl('');
  @ViewChild('inputRef', {static: true}) inputRef: ElementRef<HTMLInputElement>;

  add(value: string): void {
    this.selected.push(value);
    this.inputControl.setValue('');
    this.inputRef.nativeElement.value = ''; // Won't work without this line
  }

tim-kuteev avatar Oct 24 '19 15:10 tim-kuteev

@tim-kuteev thank you it works. I had initially just put some javascript like (HTMLInputElement document.getElementById('id')).value = ' '; (the tag characters <> were not showing the htmlinputelement so I had to remove them in the comment).

But you do it the right way with viewchild in this case. Thanks ! :)

frankrc85 avatar Dec 10 '19 23:12 frankrc85

I'm also encountering this issue. Generally it feels like the chip list and autocomplete do not work together perfectly. For example selected chips do not work in this scenario. Even if the chip has [selected]="true" it will simply not display as selected.

philmtd avatar Mar 12 '20 12:03 philmtd

Any update on this issue?

twisha avatar Sep 04 '20 15:09 twisha

I got the same issue , here's my code

<mat-form-field class="example-chip-list">
                        <mat-chip-list #chipList>
                            <mat-chip
                                    *ngFor="let primer of element.selectedPrimers"
                                    [selectable]="true"
                                    [removable]="true"
                                    (removed)="removePrimer(element,primer)">
                                {{primer.name}}
                                <mat-icon matChipRemove *ngIf="true">cancel</mat-icon>
                            </mat-chip>
                            <input
                                    placeholder="选择载体..."
                                    [formControl]="primeSearchControl"
                                    [matAutocomplete]="auto"
                                    [matChipInputFor]="chipList"
                                    [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
                                    [matChipInputAddOnBlur]="false"
                            />

                            <mat-icon matSuffix (click)="endSelectPrimer(inplace)">done_all</mat-icon>
                        </mat-chip-list>
                        <mat-autocomplete #auto="matAutocomplete" (optionSelected)="primerSelected(element,
                        $event)">
                            <mat-option *ngFor="let primer of primerOptions" [value]="primer">
                                {{primer.name}}
                            </mat-option>
                        </mat-autocomplete>
                    </mat-form-field>

the

        this.primeSearchControl.setValue('', {emitEvent: false})

in primerSelected method not work

towith avatar Oct 15 '20 08:10 towith

As a workaround it is possible to define your own copy of the [matAutoComplete] directive, which doesn't provide NG_VALUE_ACCESSOR.

import { Directive, Input } from "@angular/core";
import { MatAutocomplete, MatAutocompleteTrigger } from "@angular/material/autocomplete";

/**
 * This hack is necessary because `MatAutocompleteTrigger` provides `NG_VALUE_ACCESSOR`,
 * which doesn't make any sense when used in combination with `MatChipInput`.
 */
@Directive({
  selector: `input[matChipAutocomplete], textarea[matChipAutocomplete]`,
  host: {
    'class': 'mat-autocomplete-trigger',
    '[attr.autocomplete]': 'autocompleteAttribute',
    '[attr.role]': 'autocompleteDisabled ? null : "combobox"',
    '[attr.aria-autocomplete]': 'autocompleteDisabled ? null : "list"',
    '[attr.aria-activedescendant]': '(panelOpen && activeOption) ? activeOption.id : null',
    '[attr.aria-expanded]': 'autocompleteDisabled ? null : panelOpen.toString()',
    '[attr.aria-owns]': '(autocompleteDisabled || !panelOpen) ? null : autocomplete?.id',
    '[attr.aria-haspopup]': '!autocompleteDisabled',
    // Note: we use `focusin`, as opposed to `focus`, in order to open the panel
    // a little earlier. This avoids issues where IE delays the focusing of the input.
    '(focusin)': '_handleFocus()',
    '(blur)': '_onTouched()',
    '(input)': '_handleInput($event)',
    '(keydown)': '_handleKeydown($event)',
  },
  exportAs: 'matAutocompleteTrigger'
})
export class MatChipAutocompleteTrigger extends MatAutocompleteTrigger {
  @Input('matChipAutocomplete')
  public set matChipAutocomplete(value: MatAutocomplete) {
    this.autocomplete = value;
  }
}

mlewe avatar Mar 02 '21 16:03 mlewe

HI, I am facing the same issue, if i apply validator as "required" even when i enter fields it say formcontrol status as INVALID and value as null,

{{fruit}} cancel {{ fruit }} {{fruit}} cancel {{ fruit1 }}

<button (click)="onSubmit()">submit

import {COMMA, ENTER} from '@angular/cdk/keycodes'; import {Component, ElementRef, ViewChild} from '@angular/core'; import {FormControl} from '@angular/forms'; import {MatAutocompleteSelectedEvent, MatChipInputEvent} from '@angular/material'; import {Observable} from 'rxjs'; import {map, startWith} from 'rxjs/operators';

/**

  • @title Chips Autocomplete */ @Component({ selector: 'chips-autocomplete-example', templateUrl: 'chips-autocomplete-example.html', styleUrls: ['chips-autocomplete-example.css'] }) export class ChipsAutocompleteExample { visible: boolean = true; selectable: boolean = true; removable: boolean = true; addOnBlur: boolean = false;

separatorKeysCodes = [ENTER, COMMA];

fruitCtrl = new FormControl();

filteredFruits: Observable<any[]>;

fruits = [ 'Lemon', ];

allFruits = [ 'Apple', 'Lemon', 'Lime', 'Orange', 'Strawberry' ];

@ViewChild('fruitInput') fruitInput: ElementRef;

constructor() { this.filteredFruits = this.fruitCtrl.valueChanges.pipe( startWith(null), map((fruit: string | null) => fruit ? this.filter(fruit) : this.allFruits.slice())); }

add(event: MatChipInputEvent): void { const input = event.input; const value = event.value;

// Add our fruit
if ((value || '').trim()) {
  this.fruits.push(value.trim());
}

// Reset the input value
if (input) {
  input.value = '';
}

this.fruitCtrl.setValue(null);

}

remove(fruit: any): void { const index = this.fruits.indexOf(fruit);

if (index >= 0) {
  this.fruits.splice(index, 1);
}

}

filter(name: string) { return this.allFruits.filter(fruit => fruit.toLowerCase().indexOf(name.toLowerCase()) === 0); }

selected(event: MatAutocompleteSelectedEvent): void { this.fruits.push(event.option.viewValue); this.fruitInput.nativeElement.value = ''; this.fruitCtrl.setValue(null); } }

ansaripwd avatar Sep 03 '21 14:09 ansaripwd

@mlewe Thank you so much! I was facing the same issue and danced around for hours until I found your workaround, which works perfectly for me.

bskp avatar Sep 29 '22 15:09 bskp

This issue is still present in Angular v14.

marc-wilson avatar Nov 18 '22 18:11 marc-wilson

The issue is still there in angular and material 18.

ivorobioff avatar May 27 '24 06:05 ivorobioff

Not fixed after 6 years...

RSBlek avatar Jun 12 '24 11:06 RSBlek