Skip to content

Commit 8181759

Browse files
committed
fix(material/chips): update form control immediately when chips is removed
Fixes #30566
1 parent 15984ac commit 8181759

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

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)