diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index fdcc60891262..c24de34c1dc0 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -187,6 +187,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 385dca10c968..52a49810c181 100644 --- a/src/material/chips/chip-grid.spec.ts +++ b/src/material/chips/chip-grid.spec.ts @@ -1026,6 +1026,29 @@ describe('MatChipGrid', () => { })); }); + it('should update the form control immediately when remove button is clicked', fakeAsync(() => { + const fixture = createComponent(ChipGridWithRemoveAndFormControl, undefined, [ + NoopAnimationsModule, + ]); + + 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', @@ -1234,3 +1257,50 @@ class ChipGridWithRemove { this.chips.splice(event.chip.value, 1); } } + +@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 5dfe91e9233e..dfeca04874fc 100644 --- a/src/material/chips/chip-grid.ts +++ b/src/material/chips/chip-grid.ts @@ -280,6 +280,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()); @@ -423,6 +428,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