diff --git a/components/auto-complete/autocomplete-trigger.directive.ts b/components/auto-complete/autocomplete-trigger.directive.ts index 2dad0fafdc2..312b1f56608 100644 --- a/components/auto-complete/autocomplete-trigger.directive.ts +++ b/components/auto-complete/autocomplete-trigger.directive.ts @@ -70,7 +70,6 @@ export function getNzAutocompleteMissingPanelError(): Error { export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnDestroy { /** Bind nzAutocomplete component */ @Input() nzAutocomplete: NzAutocompleteComponent; - onChange: OnChangeType = () => {}; onTouched: OnTouchedType = () => {}; panelOpen: boolean = false; @@ -104,7 +103,7 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD } writeValue(value: NzSafeAny): void { - this.setTriggerValue(value); + Promise.resolve(null).then(() => this.setTriggerValue(value)); } registerOnChange(fn: (value: {}) => {}): void { @@ -185,7 +184,6 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD if (target.type === 'number') { value = value === '' ? null : parseFloat(value); } - if (this.previousValue !== value) { this.previousValue = value; this.onChange(value); @@ -355,10 +353,12 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD this.closePanel(); } - private setTriggerValue(value: string | number | null): void { - this.elementRef.nativeElement.value = value || ''; + private setTriggerValue(value: NzSafeAny): void { + const option = this.nzAutocomplete.getOption(value); + const displayValue = option ? option.getLabel() : value; + this.elementRef.nativeElement.value = displayValue != null ? displayValue : ''; if (!this.nzAutocomplete.nzBackfill) { - this.previousValue = value; + this.previousValue = displayValue; } } diff --git a/components/auto-complete/autocomplete.component.ts b/components/auto-complete/autocomplete.component.ts index ab04acd061c..f451d7db362 100644 --- a/components/auto-complete/autocomplete.component.ts +++ b/components/auto-complete/autocomplete.component.ts @@ -71,7 +71,13 @@ export type AutocompleteDataSource = AutocompleteDataSourceItem[] | string[] | n - {{ option }} + + {{ option && option.label ? option.label : option }} + `, @@ -199,6 +205,10 @@ export class NzAutocompleteComponent implements AfterContentInit, AfterViewInit, }, -1)!; } + getOption(value: NzSafeAny): NzAutocompleteOptionComponent | null { + return this.options.find(item => this.compareWith(value, item.nzValue)) || null; + } + updatePosition(position: NzDropDownPosition): void { this.dropDownPosition = position; this.changeDetectorRef.markForCheck(); diff --git a/components/auto-complete/autocomplete.spec.ts b/components/auto-complete/autocomplete.spec.ts index 37921d98c2b..4a3e4e5b2f1 100644 --- a/components/auto-complete/autocomplete.spec.ts +++ b/components/auto-complete/autocomplete.spec.ts @@ -30,7 +30,9 @@ describe('auto-complete', () => { NzTestAutocompleteWithoutPanelComponent, NzTestAutocompleteGroupComponent, NzTestAutocompleteWithOnPushDelayComponent, - NzTestAutocompleteWithFormComponent + NzTestAutocompleteWithFormComponent, + NzTestAutocompleteWithObjectOptionComponent, + NzTestAutocompleteDifferentValueWithFormComponent ], providers: [ { provide: Directionality, useFactory: () => ({ value: dir }) }, @@ -402,38 +404,79 @@ describe('auto-complete', () => { typeInElement('Burns', input); fixture.detectChanges(); - tick(); expect(fixture.componentInstance.inputControl.value).toBe('Burns'); fixture.componentInstance.inputControl.setValue(''); + tick(); fixture.detectChanges(); expect(input.value).toBe(''); typeInElement('Burns', input); - fixture.detectChanges(); tick(); + fixture.detectChanges(); expect(fixture.componentInstance.inputControl.value).toBe('Burns'); })); }); + describe('object option', () => { + let fixture: ComponentFixture; + let componentInstance: NzTestAutocompleteWithObjectOptionComponent; + let input: HTMLInputElement; + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(NzTestAutocompleteWithObjectOptionComponent); + componentInstance = fixture.componentInstance; + flush(); + fixture.detectChanges(); + input = fixture.debugElement.query(By.css('input')).nativeElement; + })); + + it('should select init option', fakeAsync(() => { + componentInstance.trigger.openPanel(); + const options = componentInstance.trigger.nzAutocomplete.options.toArray(); + expect(options[0].selected).toBe(true); + expect(input.value).toBe('Lucy'); + expect(componentInstance.form.get('formControl')?.value.value).toBe('lucy'); + })); + + it('should set object option', fakeAsync(() => { + componentInstance.form.get('formControl')?.setValue({ label: 'Jack', value: 'jack' }); + flush(); + fixture.detectChanges(); + componentInstance.trigger.openPanel(); + const options = componentInstance.trigger.nzAutocomplete.options.toArray(); + expect(options[0].selected).toBe(false); + expect(options[1].selected).toBe(true); + expect(input.value).toBe('Jack'); + expect(componentInstance.form.get('formControl')?.value.value).toBe('jack'); + })); + + it('should be typing other string', fakeAsync(() => { + typeInElement('string', input); + fixture.detectChanges(); + expect(componentInstance.form.get('formControl')?.value).toBe('string'); + })); + }); + describe('form', () => { let fixture: ComponentFixture; let input: HTMLInputElement; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(NzTestAutocompleteWithFormComponent); fixture.detectChanges(); input = fixture.debugElement.query(By.css('input')).nativeElement; - }); + })); - it('should set the value with form', () => { + it('should set the value with form', fakeAsync(() => { const componentInstance = fixture.componentInstance; + flush(); fixture.detectChanges(); expect(componentInstance.form.get('formControl')!.value).toContain('Burns'); expect(input.value).toContain('Burns'); - }); + })); it('should set disabled work', () => { const componentInstance = fixture.componentInstance; @@ -464,6 +507,18 @@ describe('auto-complete', () => { expect(input.disabled).toBe(true); expect(componentInstance.trigger.panelOpen).toBe(false); }); + + it('should set correct label', fakeAsync(() => { + const differentValueWithFormFixture = TestBed.createComponent(NzTestAutocompleteDifferentValueWithFormComponent); + differentValueWithFormFixture.detectChanges(); + flush(); + differentValueWithFormFixture.detectChanges(); + + const differentValueWithFormInput = differentValueWithFormFixture.debugElement.query(By.css('input')).nativeElement; + + expect(differentValueWithFormInput.value).toBe('Lucy'); + expect(differentValueWithFormFixture.componentInstance.form.get('formControl')?.value).toBe('lucy'); + })); }); describe('option groups', () => { @@ -910,9 +965,7 @@ class NzTestAutocompletePropertyComponent { } @Component({ - template: ` - - ` + template: ` ` }) class NzTestAutocompleteWithoutPanelComponent { @ViewChild(NzAutocompleteTriggerDirective, { static: false }) trigger: NzAutocompleteTriggerDirective; @@ -923,7 +976,7 @@ class NzTestAutocompleteWithoutPanelComponent { template: `
- +
` }) @@ -1023,3 +1076,60 @@ class NzTestAutocompleteWithFormComponent { this.form = this.fb.group({ formControl: 'Burns' }); } } + +@Component({ + template: ` +
+ + + + {{ option.label }} + + +
+ ` +}) +class NzTestAutocompleteDifferentValueWithFormComponent { + form: FormGroup; + options = [ + { label: 'Lucy', value: 'lucy' }, + { label: 'Jack', value: 'jack' } + ]; + @ViewChild(NzAutocompleteTriggerDirective) trigger: NzAutocompleteTriggerDirective; + + constructor(private fb: FormBuilder) { + this.form = this.fb.group({ formControl: 'lucy' }); + } +} + +@Component({ + template: ` +
+ + + {{ option.label }} + +
+ ` +}) +class NzTestAutocompleteWithObjectOptionComponent { + form: FormGroup; + options = [ + { label: 'Lucy', value: 'lucy' }, + { label: 'Jack', value: 'jack' } + ]; + @ViewChild(NzAutocompleteTriggerDirective) trigger: NzAutocompleteTriggerDirective; + + // tslint:disable-next-line: no-any + compareFun = (o1: any, o2: any) => { + if (o1) { + return typeof o1 === 'string' ? o1 === o2.label : o1.value === o2.value; + } else { + return false; + } + }; + + constructor(private fb: FormBuilder) { + this.form = this.fb.group({ formControl: { label: 'Lucy', value: 'lucy', age: 20 } }); + } +} diff --git a/components/auto-complete/demo/object-value.md b/components/auto-complete/demo/object-value.md new file mode 100755 index 00000000000..cee77d18b59 --- /dev/null +++ b/components/auto-complete/demo/object-value.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: + zh-CN: 使用对象类型选项 + en-US: Use option with object type +--- + +## zh-CN + +当 `nzValue` 和 `ngModel` 类型为 `object` 时使用 `compareWith`([SelectControlValueAccessor](https://angular.io/api/forms/SelectControlValueAccessor#caveat-option-selection)). + +## en-US + +Use `compareWith`([SelectControlValueAccessor](https://angular.io/api/forms/SelectControlValueAccessor#caveat-option-selection)) when the `nzValue` and `ngModel` type is `object`. diff --git a/components/auto-complete/demo/object-value.ts b/components/auto-complete/demo/object-value.ts new file mode 100644 index 00000000000..41a01d9723c --- /dev/null +++ b/components/auto-complete/demo/object-value.ts @@ -0,0 +1,37 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +interface Option { + label: string; + value: string; + age: number; +} + +@Component({ + selector: 'nz-demo-auto-complete-object-value', + encapsulation: ViewEncapsulation.None, + template: ` +
+ + + + {{ option.label }} + + +
+ ` +}) +export class NzDemoAutoCompleteObjectValueComponent { + inputValue: Option = { label: 'Lucy', value: 'lucy', age: 20 }; + options: Option[] = [ + { label: 'Lucy', value: 'lucy', age: 20 }, + { label: 'Jack', value: 'jack', age: 22 } + ]; + + compareFun = (o1: Option | string, o2: Option) => { + if (o1) { + return typeof o1 === 'string' ? o1 === o2.label : o1.value === o2.value; + } else { + return false; + } + }; +} diff --git a/components/auto-complete/doc/index.en-US.md b/components/auto-complete/doc/index.en-US.md index be94ae70d70..a23e65b34ca 100644 --- a/components/auto-complete/doc/index.en-US.md +++ b/components/auto-complete/doc/index.en-US.md @@ -46,6 +46,7 @@ import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete'; | `[nzWidth]` | Custom width, unit px | `number` | trigger element width | | `[nzOverlayClassName]` | Class name of the dropdown root element | `string` | - | | `[nzOverlayStyle]` | Style of the dropdown root element | `object` | - | +| `[compareWith]` | Same as [SelectControlValueAccessor](https://angular.io/api/forms/SelectControlValueAccessor#caveat-option-selection) | `(o1: any, o2: any) => boolean` | `(o1: any, o2: any) => o1===o2` | ### nz-auto-option diff --git a/components/auto-complete/doc/index.zh-CN.md b/components/auto-complete/doc/index.zh-CN.md index 423af85a5f2..16a06385936 100644 --- a/components/auto-complete/doc/index.zh-CN.md +++ b/components/auto-complete/doc/index.zh-CN.md @@ -47,6 +47,7 @@ import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete'; | `[nzWidth]` | 自定义宽度单位 px | `number` | 触发元素宽度 | | `[nzOverlayClassName]` | 下拉根元素的类名称 | `string` | - | | `[nzOverlayStyle]` | 下拉根元素的样式 | `object` | - | +| `[compareWith]` | 与 [SelectControlValueAccessor](https://angular.io/api/forms/SelectControlValueAccessor#caveat-option-selection) 相同 | `(o1: any, o2: any) => boolean` | `(o1: any, o2: any) => o1===o2` | ### nz-auto-option