fix(forms): provide a method to compare options (#13349)
Closes #13268 PR Close #13349
This commit is contained in:
committed by
Miško Hevery
parent
6c7300c7de
commit
f89d004c51
@@ -57,6 +57,31 @@ function _extractId(valueString: string): string {
|
||||
*
|
||||
* {@example forms/ts/reactiveSelectControl/reactive_select_control_example.ts region='Component'}
|
||||
*
|
||||
* ### Caveat: Option selection
|
||||
*
|
||||
* Angular uses object identity to select option. It's possible for the identities of items
|
||||
* to change while the data does not. This can happen, for example, if the items are produced
|
||||
* from an RPC to the server, and that RPC is re-run. Even if the data hasn't changed, the
|
||||
* second response will produce objects with different identities.
|
||||
*
|
||||
* To customize the default option comparison algorithm, `<select>` supports `compareWith` input.
|
||||
* `compareWith` takes a **function** which has two arguments: `option1` and `option2`.
|
||||
* If `compareWith` is given, Angular selects option by the return value of the function.
|
||||
*
|
||||
* #### Syntax
|
||||
*
|
||||
* ```
|
||||
* <select [compareWith]="compareFn" [(ngModel)]="selectedCountries">
|
||||
* <option *ngFor="let country of countries" [ngValue]="country">
|
||||
* {{country.name}}
|
||||
* </option>
|
||||
* </select>
|
||||
*
|
||||
* compareFn(c1: Country, c2: Country): boolean {
|
||||
* return c1 && c2 ? c1.id === c2.id : c1 === c2;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Note: We listen to the 'change' event because 'input' events aren't fired
|
||||
* for selects in Firefox and IE:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
|
||||
@@ -82,6 +107,16 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
@Input()
|
||||
set compareWith(fn: (o1: any, o2: any) => boolean) {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`compareWith must be a function, but received ${JSON.stringify(fn)}`);
|
||||
}
|
||||
this._compareWith = fn;
|
||||
}
|
||||
|
||||
private _compareWith: (o1: any, o2: any) => boolean = looseIdentical;
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
@@ -112,7 +147,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (const id of Array.from(this._optionMap.keys())) {
|
||||
if (looseIdentical(this._optionMap.get(id), value)) return id;
|
||||
if (this._compareWith(this._optionMap.get(id), value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,31 @@ abstract class HTMLCollection {
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a select element.
|
||||
*
|
||||
* ### Caveat: Options selection
|
||||
*
|
||||
* Angular uses object identity to select options. It's possible for the identities of items
|
||||
* to change while the data does not. This can happen, for example, if the items are produced
|
||||
* from an RPC to the server, and that RPC is re-run. Even if the data hasn't changed, the
|
||||
* second response will produce objects with different identities.
|
||||
*
|
||||
* To customize the default option comparison algorithm, `<select multiple>` supports `compareWith`
|
||||
* input. `compareWith` takes a **function** which has two arguments: `option1` and `option2`.
|
||||
* If `compareWith` is given, Angular selects options by the return value of the function.
|
||||
*
|
||||
* #### Syntax
|
||||
*
|
||||
* ```
|
||||
* <select multiple [compareWith]="compareFn" [(ngModel)]="selectedCountries">
|
||||
* <option *ngFor="let country of countries" [ngValue]="country">
|
||||
* {{country.name}}
|
||||
* </option>
|
||||
* </select>
|
||||
*
|
||||
* compareFn(c1: Country, c2: Country): boolean {
|
||||
* return c1 && c2 ? c1.id === c2.id : c1 === c2;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({
|
||||
@@ -62,6 +87,16 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
@Input()
|
||||
set compareWith(fn: (o1: any, o2: any) => boolean) {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`compareWith must be a function, but received ${JSON.stringify(fn)}`);
|
||||
}
|
||||
this._compareWith = fn;
|
||||
}
|
||||
|
||||
private _compareWith: (o1: any, o2: any) => boolean = looseIdentical;
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
@@ -119,7 +154,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (const id of Array.from(this._optionMap.keys())) {
|
||||
if (looseIdentical(this._optionMap.get(id)._value, value)) return id;
|
||||
if (this._compareWith(this._optionMap.get(id)._value, value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user