Skip to content

fix(material/chips): chips erasing values when focusing out of input #26373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 77 additions & 41 deletions src/dev-app/chips/chips-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,15 @@ <h4>With avatar, icons, and color</h4>
</button>
</mat-chip>

<mat-chip>
Razzle
</mat-chip>
<mat-chip> Razzle </mat-chip>

<mat-chip>
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar />
Mal
</mat-chip>

<mat-chip highlighted="true" color="warn">
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar />
Husi
<button matChipRemove>
<mat-icon>cancel</mat-icon>
Expand All @@ -76,22 +74,25 @@ <h4>With avatar, icons, and color</h4>
Bad
<mat-icon matChipTrailingIcon>star_border</mat-icon>
</mat-chip>

</mat-chip-set>

<h4>With Events</h4>

<mat-chip-set>
<mat-chip highlighted="true" color="warn" *ngIf="visible"
(destroyed)="displayMessage('chip destroyed')" (removed)="toggleVisible()">
<mat-chip
highlighted="true"
color="warn"
*ngIf="visible"
(destroyed)="displayMessage('chip destroyed')"
(removed)="toggleVisible()"
>
With Events
<button matChipRemove aria-label="Remove chip">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
</mat-chip-set>
<div>{{message}}</div>

</mat-card-content>
</mat-card>

Expand All @@ -100,7 +101,7 @@ <h4>With Events</h4>

<mat-card-content>
<button mat-button (click)="disabledListboxes = !disabledListboxes">
{{disabledListboxes ? "Enable" : "Disable"}}
{{disabledListboxes ? "Enable" : "Disable"}}
</button>
<button mat-button (click)="listboxesWithAvatar = !listboxesWithAvatar">
{{listboxesWithAvatar ? "Hide Avatar" : "Show Avatar"}}
Expand All @@ -123,7 +124,6 @@ <h4>Multi selection</h4>
{{hint.label}}
</mat-chip-option>
</mat-chip-listbox>

</mat-card-content>
</mat-card>

Expand All @@ -132,38 +132,41 @@ <h4>Multi selection</h4>

<mat-card-content>
<p>
The <code>&lt;mat-chip-grid&gt;</code> component pairs with the <code>matChipInputFor</code> directive
to convert user input text into chips.
They can be used inside a <code>&lt;mat-form-field&gt;</code>.
The <code>&lt;mat-chip-grid&gt;</code> component pairs with the
<code>matChipInputFor</code> directive to convert user input text into chips. They can be
used inside a <code>&lt;mat-form-field&gt;</code>.
</p>

<button mat-button (click)="disableInputs = !disableInputs">
{{disableInputs ? "Enable" : "Disable"}}
{{disableInputs ? "Enable" : "Disable"}}
</button>

<button mat-button (click)="editable = !editable">
{{editable ? "Disable editing" : "Enable editing"}}
</button>

<h4>Input is last child of chip grid</h4>

<mat-form-field class="demo-has-chip-list">
<mat-label>New Contributor...</mat-label>
<mat-chip-grid #chipGrid1 [(ngModel)]="selectedPeople" required [disabled]="disableInputs">
<mat-chip-row *ngFor="let person of people"
[editable]="editable"
(removed)="remove(person)"
(edited)="edit(person, $event)">
<mat-chip-row
*ngFor="let person of people"
[editable]="true"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this one hardcoded to true? We have a button above that is used to toggle the editing functionality.

(removed)="remove(person)"
(edited)="edit(person, $event)"
>
{{person.name}}
<button matChipRemove aria-label="Remove contributor">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
<input [disabled]="disableInputs"
[matChipInputFor]="chipGrid1"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)" />
<input
[disabled]="disableInputs"
[matChipInputFor]="chipGrid1"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)"
/>
</mat-chip-grid>
</mat-form-field>

Expand All @@ -172,17 +175,47 @@ <h4>Input is next sibling child of chip grid</h4>
<mat-form-field>
<mat-label>New Contributor...</mat-label>
<mat-chip-grid #chipGrid2 [(ngModel)]="selectedPeople" required [disabled]="disableInputs">
<mat-chip-row *ngFor="let person of people" (removed)="remove(person)">
<mat-chip-row *ngFor="let person of people" [editable]="true" (removed)="remove(person)">
{{person.name}}
<button matChipRemove aria-label="Remove contributor">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
</mat-chip-grid>
<input [matChipInputFor]="chipGrid2"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)" />
<input
[matChipInputFor]="chipGrid2"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)"
/>
</mat-form-field>

<h4>Input is last child of chip grid with Reactive Forms</h4>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a special case for when it's the last child?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach with Reactive Forms is slightly different from the one without as you are working with form control. That's why I thought that it would be better to have both versions in example. If you think that it's not worth it I can take it out.

Form Control value: {{control.value | json}}
<mat-form-field class="demo-has-chip-list">
<mat-label>New Contributor...</mat-label>
<mat-chip-grid #chipGrid3 [formControl]="control">
<mat-chip-row
*ngFor="let person of control.value; let index = index"
[editable]="true"
[displayWith]="displayFn"
[value]="person"
(removed)="removeControl(index)"
(edited)="editControl($event, index)"
>
{{person.name}}
<button matChipRemove aria-label="Remove contributor">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
<input
[disabled]="disableInputs"
[matChipInputFor]="chipGrid3"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event, true)"
/>
</mat-chip-grid>
</mat-form-field>

<p>
Expand All @@ -194,7 +227,6 @@ <h4>Options</h4>
<p>
<mat-checkbox name="addOnBlur" [(ngModel)]="addOnBlur">Add on Blur</mat-checkbox>
</p>

</mat-card-content>
</mat-card>

Expand All @@ -203,35 +235,39 @@ <h4>Options</h4>
<mat-card-content>
<h4>Stacked</h4>

<p>
You can also stack the chips if you want them on top of each other.
</p>
<p>You can also stack the chips if you want them on top of each other.</p>

<mat-chip-set class="mat-mdc-chip-set-stacked">
<mat-chip *ngFor="let aColor of availableColors" highlighted="true"
[color]="aColor.color">
<mat-chip *ngFor="let aColor of availableColors" highlighted="true" [color]="aColor.color">
{{aColor.name}}
</mat-chip>
</mat-chip-set>

<h4>NgModel with multi selection</h4>

<mat-chip-listbox [multiple]="true" [(ngModel)]="selectedColors">
<mat-chip-option *ngFor="let aColor of availableColors" [color]="aColor.color"
[value]="aColor.name">
<mat-chip-option
*ngFor="let aColor of availableColors"
[color]="aColor.color"
[value]="aColor.name"
>
{{aColor.name}}
</mat-chip-option>
</mat-chip-listbox>

The selected colors are
<span *ngFor="let color of selectedColors; let isLast=last">
{{color}}{{isLast ? '' : ', '}}</span>.
{{color}}{{isLast ? '' : ', '}}</span
>.

<h4>NgModel with single selection</h4>

<mat-chip-listbox [(ngModel)]="selectedColor">
<mat-chip-option *ngFor="let aColor of availableColors" [color]="aColor.color"
[value]="aColor.name">
<mat-chip-option
*ngFor="let aColor of availableColors"
[color]="aColor.color"
[value]="aColor.name"
>
{{aColor.name}}
</mat-chip-option>
</mat-chip-listbox>
Expand Down
49 changes: 38 additions & 11 deletions src/dev-app/chips/chips-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {CommonModule} from '@angular/common';
import {ThemePalette} from '@angular/material/core';
import {MatChipInputEvent, MatChipEditedEvent, MatChipsModule} from '@angular/material/chips';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {FormControl, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatCheckboxModule} from '@angular/material/checkbox';
Expand Down Expand Up @@ -71,11 +71,12 @@ export class ChipsDemo {
{label: 'Good for Brunch', avatar: 'B', selected: false},
];

control = new FormControl<Person[]>([], Validators.required);

// Enter, comma, semi-colon
separatorKeysCodes = [ENTER, COMMA, 186];

selectedPeople = null;

people: Person[] = [
{name: 'Kara'},
{name: 'Jeremy'},
Expand All @@ -96,18 +97,31 @@ export class ChipsDemo {
this.message = message;
}

add(event: MatChipInputEvent): void {
add(event: MatChipInputEvent, isReactiveForm?: boolean): void {
const value = (event.value || '').trim();

// Add our person
if (value) {
this.people.push({name: value});
isReactiveForm
? this.control.setValue([...this.control.value!, {name: value}])
: this.people.push({name: value});
}

// Clear the input value
event.chipInput!.clear();
}

edit(person: Person, event: MatChipEditedEvent): void {
if (!event.value.trim().length) {
this.remove(person);
}

const index = this.people.indexOf(person);
const newPeople = this.people.slice();
newPeople[index] = {...newPeople[index], name: event.value};
this.people = newPeople;
}

remove(person: Person): void {
const index = this.people.indexOf(person);

Expand All @@ -116,16 +130,29 @@ export class ChipsDemo {
}
}

edit(person: Person, event: MatChipEditedEvent): void {
if (!event.value.trim().length) {
this.remove(person);
editControl(event: MatChipEditedEvent, index: number): void {
const value = event.value.trim();
const options: any[] = [...this.control.value!];

// Remove chip if it no longer has a name
if (!value) {
options.splice(index, 1);
this.control.setValue(options);
return;
}

const index = this.people.indexOf(person);
const newPeople = this.people.slice();
newPeople[index] = {...newPeople[index], name: event.value};
this.people = newPeople;
options[index] = {name: value} as Person;
this.control.setValue(options);
}

removeControl(index: number): void {
const options: any[] = [...this.control.value!];
options.splice(index, 1);
this.control.setValue(options);
}

displayFn(obj: any): any {
return obj['name'];
}

toggleVisible(): void {
Expand Down
Loading