Skip to content

Commit 5915061

Browse files
committed
fix(material/chips): chips form control updating value immediately
Currently, when we have chips with form control, the value is updated only when its focused out. This fix will update the value of form control immediately Fixes #28065
1 parent 912bacd commit 5915061

File tree

4 files changed

+88
-1
lines changed

4 files changed

+88
-1
lines changed

goldens/material/chips/index.api.md

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ export class MatChipGrid extends MatChipSet implements AfterContentInit, AfterVi
165165
protected _allowFocusEscape(): void;
166166
_blur(): void;
167167
readonly change: EventEmitter<MatChipGridChange>;
168+
_change(): void;
168169
get chipBlurChanges(): Observable<MatChipEvent>;
169170
protected _chipInput: MatChipTextControl;
170171
// (undocumented)

src/material/chips/chip-grid.spec.ts

+72
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,44 @@ describe('MatChipGrid', () => {
10191019
}));
10201020
});
10211021

1022+
describe('chip with form control', () => {
1023+
let fixture: ComponentFixture<ChipsFormControlUpdate>;
1024+
let component: ChipsFormControlUpdate;
1025+
let nativeInput: HTMLInputElement;
1026+
let nativeButton: HTMLButtonElement;
1027+
1028+
beforeEach(() => {
1029+
fixture = createComponent(ChipsFormControlUpdate);
1030+
component = fixture.componentInstance;
1031+
nativeInput = fixture.nativeElement.querySelector('input');
1032+
nativeButton = fixture.nativeElement.querySelector('button[id="save"]');
1033+
});
1034+
1035+
it('should update the form control value when pressed enter', fakeAsync(() => {
1036+
nativeInput.value = 'hello';
1037+
nativeInput.focus();
1038+
fixture.detectChanges();
1039+
1040+
dispatchKeyboardEvent(document.activeElement!, 'keydown', ENTER);
1041+
fixture.detectChanges();
1042+
flush();
1043+
1044+
expect(component.keywordChipControl.value).not.toBeNull();
1045+
expect(component.keywordChipControl.value.length).toBe(1);
1046+
expect(nativeButton.disabled).toBeFalsy();
1047+
1048+
nativeInput.value = 'how are you ?';
1049+
nativeInput.focus();
1050+
fixture.detectChanges();
1051+
1052+
dispatchKeyboardEvent(document.activeElement!, 'keydown', ENTER);
1053+
fixture.detectChanges();
1054+
flush();
1055+
1056+
expect(component.keywordChipControl.value.length).toBe(2);
1057+
}));
1058+
});
1059+
10221060
function createComponent<T>(
10231061
component: Type<T>,
10241062
direction: Direction = 'ltr',
@@ -1228,3 +1266,37 @@ class ChipGridWithRemove {
12281266
this.chips.splice(event.chip.value, 1);
12291267
}
12301268
}
1269+
1270+
@Component({
1271+
template: `
1272+
<mat-form-field>
1273+
<mat-label>Keywords</mat-label>
1274+
<mat-chip-grid #chipGrid [formControl]="keywordChipControl">
1275+
@for (keyword of keywords; track keyword) {
1276+
<mat-chip-row>{{keyword}}</mat-chip-row>
1277+
}
1278+
</mat-chip-grid>
1279+
<input placeholder="New keyword..." [matChipInputFor]="chipGrid" (matChipInputTokenEnd)="add($event)">
1280+
</mat-form-field>
1281+
<button id="save" [disabled]="!keywordChipControl.valid">Save</button>
1282+
<button >Cancel</button>`,
1283+
standalone: false,
1284+
})
1285+
class ChipsFormControlUpdate {
1286+
keywords = new Array<string>();
1287+
keywordChipControl = new FormControl();
1288+
1289+
constructor() {
1290+
this.keywordChipControl.setValidators(Validators.required);
1291+
}
1292+
1293+
add(event: MatChipInputEvent): void {
1294+
const value = (event.value || '').trim();
1295+
1296+
if (value) {
1297+
this.keywords.push(value);
1298+
}
1299+
1300+
event.chipInput.clear();
1301+
}
1302+
}

src/material/chips/chip-grid.ts

+10
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,16 @@ export class MatChipGrid
423423
}
424424
}
425425

426+
/** When called, propagates the changes and update the immediately */
427+
_change() {
428+
if (!this.disabled) {
429+
// Timeout is needed to wait for the focus() event trigger on chip input.
430+
setTimeout(() => {
431+
this._propagateChanges();
432+
});
433+
}
434+
}
435+
426436
/**
427437
* Removes the `tabindex` from the chip grid and resets it back afterwards, allowing the
428438
* user to tab out of it. This prevents the grid from capturing focus and redirecting

src/material/chips/chip-input.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,16 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy {
193193
/** Checks to see if the (chipEnd) event needs to be emitted. */
194194
_emitChipEnd(event?: KeyboardEvent) {
195195
if (!event || (this._isSeparatorKey(event) && !event.repeat)) {
196+
const trimmedValue = this.inputElement.value?.trim();
197+
if (!this.empty && trimmedValue) {
198+
this._chipGrid._change();
199+
this._chipGrid.stateChanges.next();
200+
}
196201
this.chipEnd.emit({
197202
input: this.inputElement,
198203
value: this.inputElement.value,
199204
chipInput: this,
200205
});
201-
202206
event?.preventDefault();
203207
}
204208
}

0 commit comments

Comments
 (0)