Skip to content

Commit bd5b54c

Browse files
yurakhomitskyYurii Khomitskyi
authored andcommitted
fix(material/chips): Async chips with a delay are not highlighted
Fixes a bug in the Angular Material `chips` component where async chips with a delay were not highlighted correctly. Fixes #27370
1 parent 0d499a3 commit bd5b54c

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/t
2020
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
2121
import {By} from '@angular/platform-browser';
2222
import {MatChipListbox, MatChipOption, MatChipsModule} from './index';
23+
import {asyncScheduler, BehaviorSubject, Observable} from 'rxjs';
24+
import {observeOn} from 'rxjs/operators';
2325

2426
describe('MDC-based MatChipListbox', () => {
2527
let fixture: ComponentFixture<any>;
@@ -828,6 +830,60 @@ describe('MDC-based MatChipListbox', () => {
828830
.toBeFalsy();
829831
});
830832
});
833+
834+
describe('async multiple selection', () => {
835+
it('should select initial async chips', fakeAsync(() => {
836+
fixture = createComponent(AsyncMultiSelectionChipListbox, undefined, initFixture => {
837+
initFixture.componentInstance.control = new FormControl(['tutorial-1', 'tutorial-2']);
838+
});
839+
fixture.detectChanges();
840+
flush();
841+
842+
tick(400);
843+
fixture.detectChanges();
844+
845+
let array = fixture.componentInstance.chips.toArray();
846+
847+
expect(array.length).withContext('Expect chips not to be rendered yet').toBe(0);
848+
849+
tick(100);
850+
fixture.detectChanges();
851+
852+
array = fixture.componentInstance.chips.toArray();
853+
flush();
854+
855+
expect(array[0].selected)
856+
.withContext('Expect "tutorial-1" chip to be selected')
857+
.toBe(true);
858+
expect(array[1].selected)
859+
.withContext('Expect "tutorial-2" chip to be selected')
860+
.toBe(true);
861+
}));
862+
863+
it('should select async chips that changed over time', fakeAsync(() => {
864+
fixture = createComponent(AsyncMultiSelectionChipListbox, undefined, initFixture => {
865+
initFixture.componentInstance.control = new FormControl(['tutorial-1']);
866+
});
867+
fixture.detectChanges();
868+
flush();
869+
870+
tick(500);
871+
fixture.detectChanges();
872+
873+
fixture.componentInstance.control.setValue(['tutorial-4']);
874+
fixture.componentInstance.updateChips(['tutorial-3', 'tutorial-4']);
875+
876+
tick(500);
877+
fixture.detectChanges();
878+
879+
const array = fixture.componentInstance.chips.toArray();
880+
flush();
881+
882+
expect(array[1].selected)
883+
.withContext('Expect "tutorial-4" chip to be selected')
884+
.toBe(true);
885+
}));
886+
});
831887
});
832888
});
833889

@@ -947,6 +1003,27 @@ class MultiSelectionChipListbox {
9471003
@ViewChildren(MatChipOption) chips: QueryList<MatChipOption>;
9481004
}
9491005

1006+
@Component({
1007+
template: `
1008+
<mat-chip-listbox [multiple]="true" [formControl]="control">
1009+
<mat-chip-option *ngFor="let chip of chips$ | async" [value]="chip">
1010+
{{ chip }}
1011+
</mat-chip-option>
1012+
</mat-chip-listbox>
1013+
`,
1014+
})
1015+
class AsyncMultiSelectionChipListbox {
1016+
private _chipsSubject = new BehaviorSubject(['tutorial-1', 'tutorial-2', 'tutorial-3']);
1017+
chips$: Observable<string[]> = this._chipsSubject.pipe(observeOn(asyncScheduler, 500));
1018+
control = new FormControl<string[] | null>(null);
1019+
@ViewChild(MatChipListbox) chipListbox: MatChipListbox;
1020+
@ViewChildren(MatChipOption) chips: QueryList<MatChipOption>;
1021+
1022+
updateChips(chips: string[]): void {
1023+
this._chipsSubject.next(chips);
1024+
}
1025+
}
1026+
9501027
@Component({
9511028
template: `
9521029
<mat-chip-listbox [formControl]="control">

src/material/chips/chip-listbox.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,6 @@ export class MatChipListbox
105105
// TODO: MDC uses `grid` here
106106
protected override _defaultRole = 'listbox';
107107

108-
/** Value that was assigned before the listbox was initialized. */
109-
private _pendingInitialValue: any;
110-
111108
/** Default chip options. */
112109
private _defaultOptions = inject(MAT_CHIPS_DEFAULT_OPTIONS, {optional: true});
113110

@@ -192,7 +189,9 @@ export class MatChipListbox
192189
return this._value;
193190
}
194191
set value(value: any) {
195-
this.writeValue(value);
192+
if (this._chips && this._chips.length) {
193+
this._setSelectionByValue(value, false);
194+
}
196195
this._value = value;
197196
}
198197
protected _value: any;
@@ -210,14 +209,12 @@ export class MatChipListbox
210209
override _chips: QueryList<MatChipOption> = undefined!;
211210

212211
ngAfterContentInit() {
213-
if (this._pendingInitialValue !== undefined) {
214-
Promise.resolve().then(() => {
215-
this._setSelectionByValue(this._pendingInitialValue, false);
216-
this._pendingInitialValue = undefined;
217-
});
218-
}
219-
220212
this._chips.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
213+
if (this.value !== undefined) {
214+
Promise.resolve().then(() => {
215+
this._setSelectionByValue(this.value, false);
216+
});
217+
}
221218
// Update listbox selectable/multiple properties on chips
222219
this._syncListboxProperties();
223220
});
@@ -263,10 +260,10 @@ export class MatChipListbox
263260
* @docs-private
264261
*/
265262
writeValue(value: any): void {
266-
if (this._chips) {
267-
this._setSelectionByValue(value, false);
268-
} else if (value != null) {
269-
this._pendingInitialValue = value;
263+
if (value) {
264+
this.value = value;
265+
} else {
266+
this.value = undefined;
270267
}
271268
}
272269

0 commit comments

Comments
 (0)