Skip to content

Commit 8568cd5

Browse files
yurakhomitskymmalerba
authored andcommitted
fix(material/chips): Async chips with a delay are not highlighted (#27399)
Fixes a bug in the Angular Material `chips` component where async chips with a delay were not highlighted correctly. Fixes #27370 (cherry picked from commit 4eb06ac)
1 parent 8a99cf4 commit 8568cd5

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
@@ -18,6 +18,8 @@ import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/t
1818
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
1919
import {By} from '@angular/platform-browser';
2020
import {MatChipListbox, MatChipOption, MatChipsModule} from './index';
21+
import {asyncScheduler, BehaviorSubject, Observable} from 'rxjs';
22+
import {observeOn} from 'rxjs/operators';
2123

2224
describe('MatChipListbox', () => {
2325
let fixture: ComponentFixture<any>;
@@ -862,6 +864,60 @@ describe('MatChipListbox', () => {
862864
.toBeFalsy();
863865
});
864866
});
867+
868+
describe('async multiple selection', () => {
869+
it('should select initial async chips', fakeAsync(() => {
870+
fixture = createComponent(AsyncMultiSelectionChipListbox, undefined, initFixture => {
871+
initFixture.componentInstance.control = new FormControl(['tutorial-1', 'tutorial-2']);
872+
});
873+
fixture.detectChanges();
874+
flush();
875+
876+
tick(400);
877+
fixture.detectChanges();
878+
879+
let array = fixture.componentInstance.chips.toArray();
880+
881+
expect(array.length).withContext('Expect chips not to be rendered yet').toBe(0);
882+
883+
tick(100);
884+
fixture.detectChanges();
885+
886+
array = fixture.componentInstance.chips.toArray();
887+
flush();
888+
889+
expect(array[0].selected)
890+
.withContext('Expect "tutorial-1" chip to be selected')
891+
.toBe(true);
892+
expect(array[1].selected)
893+
.withContext('Expect "tutorial-2" chip to be selected')
894+
.toBe(true);
895+
}));
896+
897+
it('should select async chips that changed over time', fakeAsync(() => {
898+
fixture = createComponent(AsyncMultiSelectionChipListbox, undefined, initFixture => {
899+
initFixture.componentInstance.control = new FormControl(['tutorial-1']);
900+
});
901+
fixture.detectChanges();
902+
flush();
903+
904+
tick(500);
905+
fixture.detectChanges();
906+
907+
fixture.componentInstance.control.setValue(['tutorial-4']);
908+
fixture.componentInstance.updateChips(['tutorial-3', 'tutorial-4']);
909+
910+
tick(500);
911+
fixture.detectChanges();
912+
913+
const array = fixture.componentInstance.chips.toArray();
914+
flush();
915+
916+
expect(array[1].selected)
917+
.withContext('Expect "tutorial-4" chip to be selected')
918+
.toBe(true);
919+
}));
920+
});
865921
});
866922
});
867923

@@ -986,6 +1042,27 @@ class MultiSelectionChipListbox {
9861042
@ViewChildren(MatChipOption) chips: QueryList<MatChipOption>;
9871043
}
9881044

1045+
@Component({
1046+
template: `
1047+
<mat-chip-listbox [multiple]="true" [formControl]="control">
1048+
<mat-chip-option *ngFor="let chip of chips$ | async" [value]="chip">
1049+
{{ chip }}
1050+
</mat-chip-option>
1051+
</mat-chip-listbox>
1052+
`,
1053+
})
1054+
class AsyncMultiSelectionChipListbox {
1055+
private _chipsSubject = new BehaviorSubject(['tutorial-1', 'tutorial-2', 'tutorial-3']);
1056+
chips$: Observable<string[]> = this._chipsSubject.pipe(observeOn(asyncScheduler, 500));
1057+
control = new FormControl<string[] | null>(null);
1058+
@ViewChild(MatChipListbox) chipListbox: MatChipListbox;
1059+
@ViewChildren(MatChipOption) chips: QueryList<MatChipOption>;
1060+
1061+
updateChips(chips: string[]): void {
1062+
this._chipsSubject.next(chips);
1063+
}
1064+
}
1065+
9891066
@Component({
9901067
template: `
9911068
<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
@@ -103,9 +103,6 @@ export class MatChipListbox
103103
// TODO: MDC uses `grid` here
104104
protected override _defaultRole = 'listbox';
105105

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

@@ -184,7 +181,9 @@ export class MatChipListbox
184181
return this._value;
185182
}
186183
set value(value: any) {
187-
this.writeValue(value);
184+
if (this._chips && this._chips.length) {
185+
this._setSelectionByValue(value, false);
186+
}
188187
this._value = value;
189188
}
190189
protected _value: any;
@@ -202,14 +201,12 @@ export class MatChipListbox
202201
override _chips: QueryList<MatChipOption> = undefined!;
203202

204203
ngAfterContentInit() {
205-
if (this._pendingInitialValue !== undefined) {
206-
Promise.resolve().then(() => {
207-
this._setSelectionByValue(this._pendingInitialValue, false);
208-
this._pendingInitialValue = undefined;
209-
});
210-
}
211-
212204
this._chips.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
205+
if (this.value !== undefined) {
206+
Promise.resolve().then(() => {
207+
this._setSelectionByValue(this.value, false);
208+
});
209+
}
213210
// Update listbox selectable/multiple properties on chips
214211
this._syncListboxProperties();
215212
});
@@ -255,10 +252,10 @@ export class MatChipListbox
255252
* @docs-private
256253
*/
257254
writeValue(value: any): void {
258-
if (this._chips) {
259-
this._setSelectionByValue(value, false);
260-
} else if (value != null) {
261-
this._pendingInitialValue = value;
255+
if (value != null) {
256+
this.value = value;
257+
} else {
258+
this.value = undefined;
262259
}
263260
}
264261

0 commit comments

Comments
 (0)