Skip to content

Commit 2fd3421

Browse files
committed
fix(material/chips): update form control immediately when chips is removed
Currently, when we removed chips, the value is updated in form control only when we blur. This fix will update the value of form control immediately when removed Fixes #30566
1 parent ca73b56 commit 2fd3421

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

goldens/material/chips/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export class MatChipGrid extends MatChipSet implements AfterContentInit, AfterVi
187187
protected _allowFocusEscape(): void;
188188
_blur(): void;
189189
readonly change: EventEmitter<MatChipGridChange>;
190+
_change(): void;
190191
get chipBlurChanges(): Observable<MatChipEvent>;
191192
protected _chipInput: MatChipTextControl;
192193
// (undocumented)

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,29 @@ describe('MatChipGrid', () => {
10261026
}));
10271027
});
10281028

1029+
it('should update the form control immediately when remove button is clicked', fakeAsync(() => {
1030+
const fixture = createComponent(ChipGridWithRemoveAndFormControl, undefined, [
1031+
NoopAnimationsModule,
1032+
]);
1033+
1034+
const component = fixture.componentRef.instance;
1035+
1036+
flush();
1037+
const trailingActions = chipGridNativeElement.querySelectorAll<HTMLElement>(
1038+
'.mdc-evolution-chip__action--secondary',
1039+
);
1040+
const chip = chips.get(2)!;
1041+
chip.focus();
1042+
fixture.detectChanges();
1043+
1044+
trailingActions[2].click();
1045+
fixture.detectChanges();
1046+
flush();
1047+
1048+
expect(component.formControl.value?.length).toBe(3);
1049+
expect(component.formControl.value?.indexOf('tutorial')).toBe(-1);
1050+
}));
1051+
10291052
function createComponent<T>(
10301053
component: Type<T>,
10311054
direction: Direction = 'ltr',
@@ -1234,3 +1257,50 @@ class ChipGridWithRemove {
12341257
this.chips.splice(event.chip.value, 1);
12351258
}
12361259
}
1260+
1261+
@Component({
1262+
template: `
1263+
<mat-form-field>
1264+
<mat-chip-grid #chipGrid [formControl]="formControl">
1265+
@for (keyword of keywords(); track keyword) {
1266+
<mat-chip-row (removed)="removeKeyword(keyword)">
1267+
{{keyword}}
1268+
<span matChipRemove>Remove</span>
1269+
</mat-chip-row>
1270+
}
1271+
</mat-chip-grid>
1272+
<input
1273+
placeholder="New keyword..."
1274+
[matChipInputFor]="chipGrid"
1275+
/>
1276+
</mat-form-field>
1277+
`,
1278+
imports: [
1279+
MatChipGrid,
1280+
MatChipRow,
1281+
MatChipInput,
1282+
MatFormField,
1283+
MatChipRemove,
1284+
ReactiveFormsModule,
1285+
],
1286+
})
1287+
class ChipGridWithRemoveAndFormControl {
1288+
readonly keywords = signal(['angular', 'how-to', 'tutorial', 'accessibility']);
1289+
readonly formControl = new FormControl([...this.keywords()]);
1290+
1291+
constructor() {
1292+
this.formControl.setValidators(Validators.required);
1293+
}
1294+
1295+
removeKeyword(keyword: string) {
1296+
this.keywords.update(keywords => {
1297+
const index = keywords.indexOf(keyword);
1298+
if (index < 0) {
1299+
return keywords;
1300+
}
1301+
1302+
keywords.splice(index, 1);
1303+
return [...keywords];
1304+
});
1305+
}
1306+
}

src/material/chips/chip-grid.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ export class MatChipGrid
280280
this.stateChanges.next();
281281
});
282282

283+
this.chipRemovedChanges.pipe(takeUntil(this._destroyed)).subscribe(() => {
284+
this._change();
285+
this.stateChanges.next();
286+
});
287+
283288
merge(this.chipFocusChanges, this._chips.changes)
284289
.pipe(takeUntil(this._destroyed))
285290
.subscribe(() => this.stateChanges.next());
@@ -423,6 +428,16 @@ export class MatChipGrid
423428
}
424429
}
425430

431+
/** When called, propagates the changes and update the immediately */
432+
_change() {
433+
if (!this.disabled) {
434+
// Timeout is needed to wait for the focus() event trigger on chip input.
435+
setTimeout(() => {
436+
this._propagateChanges();
437+
});
438+
}
439+
}
440+
426441
/**
427442
* Removes the `tabindex` from the chip grid and resets it back afterwards, allowing the
428443
* user to tab out of it. This prevents the grid from capturing focus and redirecting

0 commit comments

Comments
 (0)