From 0783279a57bead085a9af85381d4363296b6fe6b Mon Sep 17 00:00:00 2001 From: Karan Mistry Date: Sat, 27 Sep 2025 13:51:13 +0530 Subject: [PATCH] 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 --- goldens/material/chips/index.api.md | 1 + src/material/chips/chip-grid.spec.ts | 68 ++++++++++++++++++++++++++++ src/material/chips/chip-grid.ts | 15 ++++++ 3 files changed, 84 insertions(+) diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index 0cc3258191bf..9ffa1904bb3c 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -189,6 +189,7 @@ export class MatChipGrid extends MatChipSet implements AfterContentInit, AfterVi protected _allowFocusEscape(): void; _blur(): void; readonly change: EventEmitter; + _change(): void; get chipBlurChanges(): Observable; protected _chipInput?: MatChipTextControl; // (undocumented) diff --git a/src/material/chips/chip-grid.spec.ts b/src/material/chips/chip-grid.spec.ts index 3475d16d2bb4..9837b4b3f9e2 100644 --- a/src/material/chips/chip-grid.spec.ts +++ b/src/material/chips/chip-grid.spec.ts @@ -1089,6 +1089,27 @@ describe('MatChipGrid', () => { })); }); + it('should update the form control immediately when remove button is clicked', fakeAsync(() => { + const fixture = createComponent(ChipGridWithRemoveAndFormControl, undefined, []); + + const component = fixture.componentRef.instance; + + flush(); + const trailingActions = chipGridNativeElement.querySelectorAll( + '.mdc-evolution-chip__action--secondary', + ); + const chip = chips.get(2)!; + chip.focus(); + fixture.detectChanges(); + + trailingActions[2].click(); + fixture.detectChanges(); + flush(); + + expect(component.formControl.value?.length).toBe(3); + expect(component.formControl.value?.indexOf('tutorial')).toBe(-1); + })); + function createComponent( component: Type, direction: Direction = 'ltr', @@ -1315,3 +1336,50 @@ class ChipGridWithoutInput { chips = ['Pizza', 'Pasta', 'Tacos']; placeholder: string; } + +@Component({ + template: ` + + + @for (keyword of keywords(); track keyword) { + + {{keyword}} + Remove + + } + + + + `, + imports: [ + MatChipGrid, + MatChipRow, + MatChipInput, + MatFormField, + MatChipRemove, + ReactiveFormsModule, + ], +}) +class ChipGridWithRemoveAndFormControl { + readonly keywords = signal(['angular', 'how-to', 'tutorial', 'accessibility']); + readonly formControl = new FormControl([...this.keywords()]); + + constructor() { + this.formControl.setValidators(Validators.required); + } + + removeKeyword(keyword: string) { + this.keywords.update(keywords => { + const index = keywords.indexOf(keyword); + if (index < 0) { + return keywords; + } + + keywords.splice(index, 1); + return [...keywords]; + }); + } +} diff --git a/src/material/chips/chip-grid.ts b/src/material/chips/chip-grid.ts index 24763b6c5a72..714e80173240 100644 --- a/src/material/chips/chip-grid.ts +++ b/src/material/chips/chip-grid.ts @@ -282,6 +282,11 @@ export class MatChipGrid this.stateChanges.next(); }); + this.chipRemovedChanges.pipe(takeUntil(this._destroyed)).subscribe(() => { + this._change(); + this.stateChanges.next(); + }); + merge(this.chipFocusChanges, this._chips.changes) .pipe(takeUntil(this._destroyed)) .subscribe(() => this.stateChanges.next()); @@ -435,6 +440,16 @@ export class MatChipGrid } } + /** When called, propagates the changes and update the immediately */ + _change() { + if (!this.disabled) { + // Timeout is needed to wait for the focus() event trigger on chip input. + setTimeout(() => { + this._propagateChanges(); + }); + } + } + /** * Removes the `tabindex` from the chip grid and resets it back afterwards, allowing the * user to tab out of it. This prevents the grid from capturing focus and redirecting