Skip to content

Commit

Permalink
fix(typeahead): input value properly reset when hint/ngModel are used
Browse files Browse the repository at this point in the history
together

fixes ng-bootstrap#2816
  • Loading branch information
Benoit Charbonnier committed Nov 28, 2018
1 parent aca4cbe commit c3839d3
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 24 deletions.
77 changes: 65 additions & 12 deletions src/typeahead/typeahead.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import {TestBed, ComponentFixture, async, fakeAsync, inject, tick} from '@angular/core/testing';
import {createGenericTestComponent, isBrowser} from '../test/common';
import {expectResults, getWindowLinks} from '../test/typeahead/common';

import {Component, DebugElement, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {Validators, FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {ChangeDetectionStrategy, Component, DebugElement, ViewChild} from '@angular/core';
import {async, ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {Observable, Subject, merge} from 'rxjs';

import {NgbTypeahead} from './typeahead';
import {NgbTypeaheadModule} from './typeahead.module';
import {NgbTypeaheadConfig} from './typeahead-config';
import {merge, Observable, Subject} from 'rxjs';
import {debounceTime, filter, map} from 'rxjs/operators';

import {createGenericTestComponent, isBrowser} from '../test/common';
import {expectResults, getWindowLinks} from '../test/typeahead/common';
import {ARIA_LIVE_DELAY} from '../util/accessibility/live';
import {Key} from '../util/key';
import {NgbTypeahead} from './typeahead';
import {NgbTypeaheadConfig} from './typeahead-config';
import {NgbTypeaheadModule} from './typeahead.module';



const createTestComponent = (html: string) =>
createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
Expand Down Expand Up @@ -629,6 +629,7 @@ describe('ngb-typeahead', () => {
expect(getNativeInput(compiled)).not.toHaveCssClass('ng-invalid');
});


it('should support disabled state', async(() => {
const html = `
<form>
Expand Down Expand Up @@ -910,7 +911,7 @@ describe('ngb-typeahead', () => {
});
}));

it('should restore hint when results window is dismissed', async(() => {
it('should restore hint when results window is dismissed with Esc', async(() => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="findAnywhere" [showHint]="true"/>`);
fixture.detectChanges();
Expand Down Expand Up @@ -948,6 +949,58 @@ describe('ngb-typeahead', () => {
expect(inputEl.value).toBe('on');
});
}));

it('should restore hint when results window is dismissed with click outside', async(async() => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="findAnywhere" [showHint]="true"/>`);
fixture.detectChanges();
const compiled = fixture.nativeElement;
const inputEl = getNativeInput(compiled);

await fixture.whenStable();
changeInput(compiled, 'on');
fixture.detectChanges();

expect(getWindow(compiled)).not.toBeNull();
expectInputValue(compiled, 'one');
expect(inputEl.selectionStart).toBe(2);
expect(inputEl.selectionEnd).toBe(3);

document.body.click();
fixture.detectChanges();
expectInputValue(compiled, 'on');
}));

describe('should clear input properly when model get reset to empty string', () => {
[`<button (click)="model = ''">reset</button>
<input type="text" [(ngModel)]="model" [ngbTypeahead]="findObjects" />`, `<button (click)="model = ''">reset</button>
<input type="text" [(ngModel)]="model" [showHint]="true" [ngbTypeahead]="findObjects" />`].forEach((html, index) => {
it(`${index === 0 ? 'without' : 'with'} showHint`, async(async() => {
const fixture = createTestComponent(html);
await fixture.whenStable();

fixture.detectChanges();
const compiled = fixture.nativeElement;
changeInput(compiled, 'o');
fixture.detectChanges();

await fixture.whenStable();
fixture.detectChanges();

// Making sure window is opened before clicking reset button
expect(getWindow(compiled)).not.toBeNull();
compiled.querySelector('button').click();
fixture.detectChanges();

await fixture.whenStable();
fixture.detectChanges();

expect(fixture.componentInstance.model).toBe('');
expectInputValue(compiled, '');
}));
});
});

});

describe('Custom config', () => {
Expand Down
34 changes: 22 additions & 12 deletions src/typeahead/typeahead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ import {
Output,
Renderer2,
TemplateRef,
ViewContainerRef
ViewContainerRef,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {Observable, BehaviorSubject, Subscription, fromEvent} from 'rxjs';
import {positionElements, PlacementArray} from '../util/positioning';
import {NgbTypeaheadWindow, ResultTemplateContext} from './typeahead-window';
import {PopupService} from '../util/popup';
import {toString, isDefined} from '../util/util';
import {Key} from '../util/key';
import {BehaviorSubject, fromEvent, Observable, Subscription} from 'rxjs';
import {map, switchMap, tap} from 'rxjs/operators';

import {Live} from '../util/accessibility/live';
import {Key} from '../util/key';
import {PopupService} from '../util/popup';
import {PlacementArray, positionElements} from '../util/positioning';
import {isDefined, toString} from '../util/util';
import {NgbTypeaheadConfig} from './typeahead-config';
import {map, switchMap, tap} from 'rxjs/operators';
import {NgbTypeaheadWindow, ResultTemplateContext} from './typeahead-window';



const NGB_TYPEAHEAD_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
Expand Down Expand Up @@ -181,7 +184,7 @@ export class NgbTypeahead implements ControlValueAccessor,

ngOnInit(): void {
const inputValues$ = this._valueChanges.pipe(tap(value => {
this._inputValueBackup = value;
this._inputValueBackup = this.showHint ? value : null;
if (this.editable) {
this._onChange(value);
}
Expand All @@ -206,7 +209,12 @@ export class NgbTypeahead implements ControlValueAccessor,

registerOnTouched(fn: () => any): void { this._onTouched = fn; }

writeValue(value) { this._writeInputValue(this._formatItemForInput(value)); }
writeValue(value) {
this._writeInputValue(this._formatItemForInput(value));
if (this.showHint) {
this._inputValueBackup = value;
}
}

setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
Expand All @@ -224,7 +232,9 @@ export class NgbTypeahead implements ControlValueAccessor,
dismissPopup() {
if (this.isPopupOpen()) {
this._closePopup();
this._writeInputValue(this._inputValueBackup);
if (this.showHint && this._inputValueBackup !== null) {
this._writeInputValue(this._inputValueBackup);
}
}
}

Expand Down Expand Up @@ -323,7 +333,7 @@ export class NgbTypeahead implements ControlValueAccessor,
this._elementRef.nativeElement['setSelectionRange'].apply(
this._elementRef.nativeElement, [this._inputValueBackup.length, formattedVal.length]);
} else {
this.writeValue(this._windowRef.instance.getActive());
this._writeInputValue(formattedVal);
}
}
}
Expand Down

0 comments on commit c3839d3

Please sign in to comment.