Skip to content

Commit f69992f

Browse files
yurakhomitskyYurii Khomitskyi
authored and
Yurii Khomitskyi
committed
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 f69992f

File tree

2 files changed

+85
-17
lines changed

2 files changed

+85
-17
lines changed

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

Lines changed: 79 additions & 2 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>;
@@ -462,8 +464,8 @@ describe('MDC-based MatChipListbox', () => {
462464
fixture.detectChanges();
463465

464466
expect(fixture.componentInstance.chipListbox.value)
465-
.withContext('Expect value to be undefined')
466-
.toBeUndefined();
467+
.withContext('Expect value to be null')
468+
.toBeNull();
467469
expect(array[2].selected).withContext('Expect disabled chip not selected').toBeFalsy();
468470
expect(fixture.componentInstance.chipListbox.selected)
469471
.withContext('Expect no selected chips')
@@ -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: 6 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,10 @@ export class MatChipListbox
210209
override _chips: QueryList<MatChipOption> = undefined!;
211210

212211
ngAfterContentInit() {
213-
if (this._pendingInitialValue !== undefined) {
212+
this._chips.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
214213
Promise.resolve().then(() => {
215-
this._setSelectionByValue(this._pendingInitialValue, false);
216-
this._pendingInitialValue = undefined;
214+
this._setSelectionByValue(this.value, false);
217215
});
218-
}
219-
220-
this._chips.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
221216
// Update listbox selectable/multiple properties on chips
222217
this._syncListboxProperties();
223218
});
@@ -263,11 +258,7 @@ export class MatChipListbox
263258
* @docs-private
264259
*/
265260
writeValue(value: any): void {
266-
if (this._chips) {
267-
this._setSelectionByValue(value, false);
268-
} else if (value != null) {
269-
this._pendingInitialValue = value;
270-
}
261+
this.value = value;
271262
}
272263

273264
/**

0 commit comments

Comments
 (0)