Skip to content

Commit

Permalink
feat(module:autocomplete): support values whit object type (#4996)
Browse files Browse the repository at this point in the history
close #4981
  • Loading branch information
hsuanxyz authored Apr 26, 2020
1 parent 4117967 commit 4bfbbf7
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 18 deletions.
12 changes: 6 additions & 6 deletions components/auto-complete/autocomplete-trigger.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}

Expand Down
12 changes: 11 additions & 1 deletion components/auto-complete/autocomplete.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ export type AutocompleteDataSource = AutocompleteDataSourceItem[] | string[] | n
<ng-content></ng-content>
</ng-template>
<ng-template #optionsTemplate>
<nz-auto-option *ngFor="let option of nzDataSource" [nzValue]="option">{{ option }}</nz-auto-option>
<nz-auto-option
*ngFor="let option of nzDataSource"
[nzValue]="option"
[nzLabel]="option && option.label ? option.label : $any(option)"
>
{{ option && option.label ? option.label : option }}
</nz-auto-option>
</ng-template>
</ng-template>
`,
Expand Down Expand Up @@ -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();
Expand Down
132 changes: 121 additions & 11 deletions components/auto-complete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ describe('auto-complete', () => {
NzTestAutocompleteWithoutPanelComponent,
NzTestAutocompleteGroupComponent,
NzTestAutocompleteWithOnPushDelayComponent,
NzTestAutocompleteWithFormComponent
NzTestAutocompleteWithFormComponent,
NzTestAutocompleteWithObjectOptionComponent,
NzTestAutocompleteDifferentValueWithFormComponent
],
providers: [
{ provide: Directionality, useFactory: () => ({ value: dir }) },
Expand Down Expand Up @@ -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<NzTestAutocompleteWithObjectOptionComponent>;
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<NzTestAutocompleteWithFormComponent>;
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;
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -910,9 +965,7 @@ class NzTestAutocompletePropertyComponent {
}

@Component({
template: `
<input [nzAutocomplete]="auto" />
`
template: ` <input [nzAutocomplete]="auto" /> `
})
class NzTestAutocompleteWithoutPanelComponent {
@ViewChild(NzAutocompleteTriggerDirective, { static: false }) trigger: NzAutocompleteTriggerDirective;
Expand All @@ -923,7 +976,7 @@ class NzTestAutocompleteWithoutPanelComponent {
template: `
<div>
<input [nzAutocomplete]="auto" />
<nz-autocomplete [nzDataSource]="options" #auto> </nz-autocomplete>
<nz-autocomplete [nzDataSource]="options" #auto></nz-autocomplete>
</div>
`
})
Expand Down Expand Up @@ -1023,3 +1076,60 @@ class NzTestAutocompleteWithFormComponent {
this.form = this.fb.group({ formControl: 'Burns' });
}
}

@Component({
template: `
<form [formGroup]="form">
<input formControlName="formControl" [nzAutocomplete]="auto" />
<nz-autocomplete #auto>
<nz-auto-option *ngFor="let option of options" [nzValue]="option.value" [nzLabel]="option.label">
{{ option.label }}
</nz-auto-option>
</nz-autocomplete>
</form>
`
})
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: `
<form [formGroup]="form">
<input formControlName="formControl" [nzAutocomplete]="auto" />
<nz-autocomplete #auto [compareWith]="compareFun">
<nz-auto-option *ngFor="let option of options" [nzValue]="option" [nzLabel]="option.label">{{ option.label }}</nz-auto-option>
</nz-autocomplete>
</form>
`
})
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 } });
}
}
14 changes: 14 additions & 0 deletions components/auto-complete/demo/object-value.md
Original file line number Diff line number Diff line change
@@ -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`.
37 changes: 37 additions & 0 deletions components/auto-complete/demo/object-value.ts
Original file line number Diff line number Diff line change
@@ -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: `
<div class="example-input">
<input placeholder="input here" nz-input [(ngModel)]="inputValue" [nzAutocomplete]="auto" />
<nz-autocomplete #auto [compareWith]="compareFun">
<nz-auto-option *ngFor="let option of options" [nzValue]="option" [nzLabel]="option.label">
{{ option.label }}
</nz-auto-option>
</nz-autocomplete>
</div>
`
})
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;
}
};
}
1 change: 1 addition & 0 deletions components/auto-complete/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions components/auto-complete/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 4bfbbf7

Please sign in to comment.