Skip to content

Commit

Permalink
fix(material/chips): Async chips with a delay are not highlighted
Browse files Browse the repository at this point in the history
Fixes a bug in the Angular Material `chips` component where async chips with a delay were not highlighted correctly.

Fixes #27370
  • Loading branch information
yurakhomitsky authored and Yurii Khomitskyi committed Sep 27, 2023
1 parent 0d499a3 commit bd5b54c
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 15 deletions.
77 changes: 77 additions & 0 deletions src/material/chips/chip-listbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/t
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {MatChipListbox, MatChipOption, MatChipsModule} from './index';
import {asyncScheduler, BehaviorSubject, Observable} from 'rxjs';
import {observeOn} from 'rxjs/operators';

describe('MDC-based MatChipListbox', () => {
let fixture: ComponentFixture<any>;
Expand Down Expand Up @@ -828,6 +830,60 @@ describe('MDC-based MatChipListbox', () => {
.toBeFalsy();
});
});

describe('async multiple selection', () => {
it('should select initial async chips', fakeAsync(() => {
fixture = createComponent(AsyncMultiSelectionChipListbox, undefined, initFixture => {
initFixture.componentInstance.control = new FormControl(['tutorial-1', 'tutorial-2']);
});
fixture.detectChanges();
flush();

tick(400);
fixture.detectChanges();

let array = fixture.componentInstance.chips.toArray();

expect(array.length).withContext('Expect chips not to be rendered yet').toBe(0);

tick(100);
fixture.detectChanges();

array = fixture.componentInstance.chips.toArray();
flush();

expect(array[0].selected)
.withContext('Expect "tutorial-1" chip to be selected')
.toBe(true);
expect(array[1].selected)
.withContext('Expect "tutorial-2" chip to be selected')
.toBe(true);
}));

it('should select async chips that changed over time', fakeAsync(() => {
fixture = createComponent(AsyncMultiSelectionChipListbox, undefined, initFixture => {
initFixture.componentInstance.control = new FormControl(['tutorial-1']);
});
fixture.detectChanges();
flush();

tick(500);
fixture.detectChanges();

fixture.componentInstance.control.setValue(['tutorial-4']);
fixture.componentInstance.updateChips(['tutorial-3', 'tutorial-4']);

tick(500);
fixture.detectChanges();

const array = fixture.componentInstance.chips.toArray();
flush();

expect(array[1].selected)
.withContext('Expect "tutorial-4" chip to be selected')
.toBe(true);
}));
});
});
});

Expand Down Expand Up @@ -947,6 +1003,27 @@ class MultiSelectionChipListbox {
@ViewChildren(MatChipOption) chips: QueryList<MatChipOption>;
}

@Component({
template: `
<mat-chip-listbox [multiple]="true" [formControl]="control">
<mat-chip-option *ngFor="let chip of chips$ | async" [value]="chip">
{{ chip }}
</mat-chip-option>
</mat-chip-listbox>
`,
})
class AsyncMultiSelectionChipListbox {
private _chipsSubject = new BehaviorSubject(['tutorial-1', 'tutorial-2', 'tutorial-3']);
chips$: Observable<string[]> = this._chipsSubject.pipe(observeOn(asyncScheduler, 500));
control = new FormControl<string[] | null>(null);
@ViewChild(MatChipListbox) chipListbox: MatChipListbox;
@ViewChildren(MatChipOption) chips: QueryList<MatChipOption>;

updateChips(chips: string[]): void {
this._chipsSubject.next(chips);
}
}

@Component({
template: `
<mat-chip-listbox [formControl]="control">
Expand Down
27 changes: 12 additions & 15 deletions src/material/chips/chip-listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ export class MatChipListbox
// TODO: MDC uses `grid` here
protected override _defaultRole = 'listbox';

/** Value that was assigned before the listbox was initialized. */
private _pendingInitialValue: any;

/** Default chip options. */
private _defaultOptions = inject(MAT_CHIPS_DEFAULT_OPTIONS, {optional: true});

Expand Down Expand Up @@ -192,7 +189,9 @@ export class MatChipListbox
return this._value;
}
set value(value: any) {
this.writeValue(value);
if (this._chips && this._chips.length) {
this._setSelectionByValue(value, false);
}
this._value = value;
}
protected _value: any;
Expand All @@ -210,14 +209,12 @@ export class MatChipListbox
override _chips: QueryList<MatChipOption> = undefined!;

ngAfterContentInit() {
if (this._pendingInitialValue !== undefined) {
Promise.resolve().then(() => {
this._setSelectionByValue(this._pendingInitialValue, false);
this._pendingInitialValue = undefined;
});
}

this._chips.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
if (this.value !== undefined) {
Promise.resolve().then(() => {
this._setSelectionByValue(this.value, false);
});
}
// Update listbox selectable/multiple properties on chips
this._syncListboxProperties();
});
Expand Down Expand Up @@ -263,10 +260,10 @@ export class MatChipListbox
* @docs-private
*/
writeValue(value: any): void {
if (this._chips) {
this._setSelectionByValue(value, false);
} else if (value != null) {
this._pendingInitialValue = value;
if (value) {
this.value = value;
} else {
this.value = undefined;
}
}

Expand Down

0 comments on commit bd5b54c

Please sign in to comment.