Skip to content

Commit

Permalink
fix(module:auto-complete): default value not selected (#4366)
Browse files Browse the repository at this point in the history
* fix(module:auto-complete): default value not selected

close #4362

* docs(module:auto-complete): remove OnPush
  • Loading branch information
hsuanxyz authored and vthinkxie committed Nov 8, 2019
1 parent 8e9e6a9 commit 09f1ec6
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 58 deletions.
7 changes: 4 additions & 3 deletions components/auto-complete/demo/certain-category.ts
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';

export interface AutocompleteOptionGroups {
title: string;
Expand All @@ -9,7 +9,6 @@ export interface AutocompleteOptionGroups {
@Component({
selector: 'nz-demo-auto-complete-certain-category',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="example-input">
<nz-input-group nzSize="large" [nzSuffix]="suffixIcon">
Expand All @@ -32,7 +31,7 @@ export interface AutocompleteOptionGroups {
<a class="more-link" href="https://www.google.com/search?q=ng+zorro" target="_blank">更多</a>
</span>
</ng-template>
<nz-auto-option *ngFor="let option of group.children" [nzLabel]="option.title" [nzValue]="option">
<nz-auto-option *ngFor="let option of group.children" [nzLabel]="option.title" [nzValue]="option.title">
{{ option.title }}
<span class="certain-search-item-count">{{ option.count }} 人 关注</span>
</nz-auto-option>
Expand All @@ -58,6 +57,8 @@ export class NzDemoAutoCompleteCertainCategoryComponent implements OnInit {
inputValue: string;
optionGroups: AutocompleteOptionGroups[];

constructor() {}

onChange(value: string): void {
console.log(value);
}
Expand Down
8 changes: 4 additions & 4 deletions components/auto-complete/demo/non-case-sensitive.ts
Expand Up @@ -9,10 +9,10 @@ import { Component, ViewEncapsulation } from '@angular/core';
placeholder="try to type \`b\`"
nz-input
[(ngModel)]="inputValue"
(input)="onInput($event.target?.value)"
(ngModelChange)="onChange($event)"
[nzAutocomplete]="auto"
/>
<nz-autocomplete [nzDataSource]="filteredOptions" #auto> </nz-autocomplete>
<nz-autocomplete [nzDataSource]="filteredOptions" #auto></nz-autocomplete>
</div>
`
})
Expand All @@ -25,7 +25,7 @@ export class NzDemoAutoCompleteNonCaseSensitiveComponent {
this.filteredOptions = this.options;
}

onInput(value: string): void {
this.filteredOptions = this.options.filter(option => option.toLowerCase().indexOf(value.toLowerCase()) === 0);
onChange(value: string): void {
this.filteredOptions = this.options.filter(option => option.toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
}
5 changes: 3 additions & 2 deletions components/auto-complete/demo/options.ts
Expand Up @@ -9,7 +9,7 @@ import { Component, ViewEncapsulation } from '@angular/core';
placeholder="input here"
nz-input
[(ngModel)]="inputValue"
(ngModelChange)="onChange($event)"
(input)="onInput($event)"
[nzAutocomplete]="auto"
/>
<nz-autocomplete #auto>
Expand All @@ -22,7 +22,8 @@ export class NzDemoAutoCompleteOptionsComponent {
inputValue: string;
options: string[] = [];

onChange(value: string): void {
onInput(e: Event): void {
const value = (e.target as HTMLInputElement).value;
if (!value || value.indexOf('@') >= 0) {
this.options = [];
} else {
Expand Down
34 changes: 24 additions & 10 deletions components/auto-complete/demo/uncertain-category.ts
Expand Up @@ -10,7 +10,7 @@ import { Component, ViewEncapsulation } from '@angular/core';
placeholder="input here"
nz-input
[(ngModel)]="inputValue"
(ngModelChange)="onChange($event)"
(input)="onChange($event)"
[nzAutocomplete]="auto"
/>
</nz-input-group>
Expand All @@ -20,22 +20,35 @@ import { Component, ViewEncapsulation } from '@angular/core';
</button>
</ng-template>
<nz-autocomplete #auto>
<nz-auto-option *ngFor="let option of options" [nzValue]="option.category">
{{ option.value }} 在
<a [href]="'https://s.taobao.com/search?q=' + option.query" target="_blank" rel="noopener noreferrer">
<nz-auto-option class="global-search-item" *ngFor="let option of options" [nzValue]="option.category">
Found {{ option.value }} on
<a
class="global-search-item-desc"
[href]="'https://s.taobao.com/search?q=' + option.value"
target="_blank"
rel="noopener noreferrer"
>
{{ option.category }}
</a>
区块中
<span class="global-search-item-count">约 {{ option.count }} 个结果</span>
<span class="global-search-item-count">{{ option.count }} results</span>
</nz-auto-option>
</nz-autocomplete>
</div>
`,
styles: [
`
.global-search-item {
display: flex;
}
.global-search-item-desc {
flex: auto;
text-overflow: ellipsis;
overflow: hidden;
}
.global-search-item-count {
position: absolute;
right: 16px;
flex: none;
}
`
]
Expand All @@ -44,8 +57,9 @@ export class NzDemoAutoCompleteUncertainCategoryComponent {
inputValue: string;
options: Array<{ value: string; category: string; count: number }> = [];

onChange(value: string): void {
this.options = new Array(this.getRandomInt(15, 5))
onChange(e: Event): void {
const value = (e.target as HTMLInputElement).value;
this.options = new Array(this.getRandomInt(5, 15))
.join('.')
.split('.')
.map((_item, idx) => ({
Expand Down
6 changes: 4 additions & 2 deletions components/auto-complete/nz-autocomplete-option.component.ts
Expand Up @@ -54,10 +54,12 @@ export class NzAutocompleteOptionComponent {

constructor(private changeDetectorRef: ChangeDetectorRef, private element: ElementRef) {}

select(): void {
select(emit: boolean = true): void {
this.selected = true;
this.changeDetectorRef.markForCheck();
this.emitSelectionChangeEvent();
if (emit) {
this.emitSelectionChangeEvent();
}
}

deselect(): void {
Expand Down
51 changes: 32 additions & 19 deletions components/auto-complete/nz-autocomplete-trigger.directive.ts
Expand Up @@ -26,14 +26,15 @@ import {
ExistingProvider,
Inject,
Input,
NgZone,
OnDestroy,
Optional,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { fromEvent, merge, Subscription } from 'rxjs';
import { delay, distinct, map } from 'rxjs/operators';
import { delay, distinct, map, take, tap } from 'rxjs/operators';

import { NzAutocompleteOptionComponent } from './nz-autocomplete-option.component';
import { NzAutocompleteComponent } from './nz-autocomplete.component';
Expand Down Expand Up @@ -92,8 +93,9 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD

constructor(
private elementRef: ElementRef,
private _overlay: Overlay,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private ngZone: NgZone,
// tslint:disable-next-line:no-any
@Optional() @Inject(DOCUMENT) private document: any
) {}
Expand Down Expand Up @@ -124,6 +126,7 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
openPanel(): void {
this.previousValue = this.elementRef.nativeElement.value;
this.attachOverlay();
this.updateStatus();
}

closePanel(): void {
Expand Down Expand Up @@ -210,8 +213,16 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
* Subscription data source changes event
*/
private subscribeOptionsChange(): Subscription {
return this.nzAutocomplete.options.changes.pipe(delay(0)).subscribe(() => {
const firstStable = this.ngZone.onStable.asObservable().pipe(take(1));
const optionChanges = this.nzAutocomplete.options.changes.pipe(
tap(() => this.positionStrategy.reapplyLastPosition()),
delay(0)
);
return merge(firstStable, optionChanges).subscribe(() => {
this.resetActiveItem();
if (this.panelOpen) {
this.overlayRef!.updatePosition();
}
});
}

Expand Down Expand Up @@ -252,10 +263,11 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
return this.positionStrategy.positionChanges
.pipe(
map((position: ConnectedOverlayPositionChange) => position.connectionPair.originY),
distinct()
distinct(),
delay(0)
)
.subscribe((position: VerticalConnectionPos) => {
this.nzAutocomplete.dropDownPosition = position;
this.nzAutocomplete.updatePosition(position);
});
}

Expand All @@ -269,7 +281,7 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
}

if (!this.overlayRef) {
this.overlayRef = this._overlay.create(this.getOverlayConfig());
this.overlayRef = this.overlay.create(this.getOverlayConfig());
}

if (this.overlayRef && !this.overlayRef.hasAttached()) {
Expand All @@ -279,15 +291,14 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
this.overlayBackdropClickSubscription = this.subscribeOverlayBackdropClick();
this.optionsChangeSubscription = this.subscribeOptionsChange();
}

this.nzAutocomplete.isOpen = this.panelOpen = true;
}

private updateStatus(): void {
if (this.overlayRef) {
this.overlayRef.updateSize({ width: this.nzAutocomplete.nzWidth || this.getHostWidth() });
}
this.nzAutocomplete.setVisibility();
this.overlayRef.updateSize({ width: this.nzAutocomplete.nzWidth || this.getHostWidth() });
setTimeout(() => {
if (this.overlayRef) {
this.overlayRef.updatePosition();
}
}, 150);
this.resetActiveItem();
if (this.activeOption) {
this.activeOption.scrollIntoViewIfNeeded();
Expand All @@ -303,7 +314,7 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
private getOverlayConfig(): OverlayConfig {
return new OverlayConfig({
positionStrategy: this.getOverlayPosition(),
scrollStrategy: this._overlay.scrollStrategies.reposition(),
scrollStrategy: this.overlay.scrollStrategies.reposition(),
// default host element width
width: this.nzAutocomplete.nzWidth || this.getHostWidth()
});
Expand All @@ -322,19 +333,21 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }),
new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' })
];
this.positionStrategy = this._overlay
this.positionStrategy = this.overlay
.position()
.flexibleConnectedTo(this.getConnectedElement())
.withPositions(positions)
.withFlexibleDimensions(false)
.withPush(false);
.withPush(false)
.withPositions(positions);
return this.positionStrategy;
}

private resetActiveItem(): void {
const index = this.nzAutocomplete.getOptionIndex(this.nzAutocomplete.activeItem);
if (this.nzAutocomplete.activeItem && index !== -1) {
const index = this.nzAutocomplete.getOptionIndex(this.previousValue);
this.nzAutocomplete.clearSelectedOptions(null, true);
if (index !== -1) {
this.nzAutocomplete.setActiveItem(index);
this.nzAutocomplete.activeItem.select(false);
} else {
this.nzAutocomplete.setActiveItem(this.nzAutocomplete.nzDefaultActiveFirstOption ? 0 : -1);
}
Expand Down
4 changes: 3 additions & 1 deletion components/auto-complete/nz-autocomplete.component.html
Expand Up @@ -4,7 +4,9 @@
[@.disabled]="noAnimation?.nzNoAnimation"
[nzNoAnimation]="noAnimation?.nzNoAnimation"
[@slideMotion]="dropDownPosition"
[class.ant-select-dropdown-hidden]="!showPanel" [ngClass]="nzOverlayClassName" [ngStyle]="nzOverlayStyle">
[class.ant-select-dropdown-hidden]="!showPanel"
[ngClass]="nzOverlayClassName"
[ngStyle]="nzOverlayStyle">
<div style="overflow: auto;">
<ul class="ant-select-dropdown-menu ant-select-dropdown-menu-root ant-select-dropdown-menu-vertical"
role="menu"
Expand Down
33 changes: 24 additions & 9 deletions components/auto-complete/nz-autocomplete.component.ts
Expand Up @@ -7,6 +7,7 @@
*/

import {
AfterContentInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Expand All @@ -29,7 +30,7 @@ import {
import { defer, merge, Observable, Subscription } from 'rxjs';
import { filter, switchMap, take } from 'rxjs/operators';

import { slideMotion, InputBoolean, NzDropDownPosition, NzNoAnimationDirective } from 'ng-zorro-antd/core';
import { slideMotion, CompareWith, InputBoolean, NzDropDownPosition, NzNoAnimationDirective } from 'ng-zorro-antd/core';

import { NzAutocompleteOptionComponent, NzOptionSelectionChange } from './nz-autocomplete-option.component';

Expand Down Expand Up @@ -61,18 +62,19 @@ export type AutocompleteDataSource = AutocompleteDataSourceItem[] | string[] | n
`
]
})
export class NzAutocompleteComponent implements AfterViewInit, OnDestroy {
export class NzAutocompleteComponent implements AfterContentInit, AfterViewInit, OnDestroy {
@Input() nzWidth: number;
@Input() nzOverlayClassName = '';
@Input() nzOverlayStyle: { [key: string]: string } = {};
@Input() @InputBoolean() nzDefaultActiveFirstOption = true;
@Input() @InputBoolean() nzBackfill = false;
@Input() compareWith: CompareWith = (o1, o2) => o1 === o2;
@Input() nzDataSource: AutocompleteDataSource;
@Output() readonly selectionChange: EventEmitter<NzAutocompleteOptionComponent> = new EventEmitter<
NzAutocompleteOptionComponent
>();

showPanel: boolean = false;
showPanel: boolean = true;
isOpen: boolean = false;
activeItem: NzAutocompleteOptionComponent;
dropDownPosition: NzDropDownPosition = 'bottom';
Expand Down Expand Up @@ -121,8 +123,16 @@ export class NzAutocompleteComponent implements AfterViewInit, OnDestroy {
@Host() @Optional() public noAnimation?: NzNoAnimationDirective
) {}

ngAfterContentInit(): void {
if (!this.nzDataSource) {
this.optionsInit();
}
}

ngAfterViewInit(): void {
this.optionsInit();
if (this.nzDataSource) {
this.optionsInit();
}
}

ngOnDestroy(): void {
Expand Down Expand Up @@ -156,17 +166,22 @@ export class NzAutocompleteComponent implements AfterViewInit, OnDestroy {
this.setActiveItem(previousIndex);
}

getOptionIndex(option?: NzAutocompleteOptionComponent): number {
// tslint:disable-next-line:no-any
getOptionIndex(value: any): number {
return this.options.reduce((result: number, current: NzAutocompleteOptionComponent, index: number) => {
return result === -1 ? (option === current ? index : -1) : result;
return result === -1 ? (this.compareWith(value, current.nzValue) ? index : -1) : result;
}, -1)!;
}

updatePosition(position: NzDropDownPosition): void {
this.dropDownPosition = position;
this.changeDetectorRef.markForCheck();
}

private optionsInit(): void {
this.setVisibility();
this.subscribeOptionChanges();
const changes = this.nzDataSource ? this.fromDataSourceOptions.changes : this.fromContentOptions.changes;

// async
this.dataSourceChangeSubscription = changes.subscribe(e => {
if (!e.dirty && this.isOpen) {
Expand All @@ -179,7 +194,7 @@ export class NzAutocompleteComponent implements AfterViewInit, OnDestroy {
/**
* Clear the status of options
*/
private clearSelectedOptions(skip?: NzAutocompleteOptionComponent, deselect: boolean = false): void {
clearSelectedOptions(skip?: NzAutocompleteOptionComponent | null, deselect: boolean = false): void {
this.options.forEach(option => {
if (option !== skip) {
if (deselect) {
Expand All @@ -198,7 +213,7 @@ export class NzAutocompleteComponent implements AfterViewInit, OnDestroy {
event.source.select();
event.source.setActiveStyles();
this.activeItem = event.source;
this.activeItemIndex = this.getOptionIndex(this.activeItem);
this.activeItemIndex = this.getOptionIndex(this.activeItem.nzValue);
this.clearSelectedOptions(event.source, true);
this.selectionChange.emit(event.source);
});
Expand Down

0 comments on commit 09f1ec6

Please sign in to comment.