Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6193c38

Browse files
fix(input-directive): input does not remove ng-invalid with valid model value
When the user enters an invalid date (i.e. past date or total non-sense) the input validates and adds 'ng-invalid' to the input. Then the model is updated to a valid input (i.e. not through the input but rather the date-picker or an async call returning a value) the directive correctly sets `ng-valid` on the input. Prior to this fix, the input would not re-validate when changes to the model happened. Fix #448
1 parent ceb404a commit 6193c38

File tree

3 files changed

+81
-33
lines changed

3 files changed

+81
-33
lines changed

‎package-lock.json‎

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/lib/dl-date-time-input/dl-date-time-input.directive.ts‎

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Directive, ElementRef, EventEmitter, forwardRef,HostListener, Inject, Input, Output, Renderer2} from '@angular/core';
1+
import {Directive, ElementRef, EventEmitter, HostListener, Inject, Input, Output, Renderer2} from '@angular/core';
22
import {
33
AbstractControl,
44
ControlValueAccessor,
@@ -27,16 +27,16 @@ const moment = _moment;
2727
@Directive({
2828
selector: 'input[dlDateTimeInput]',
2929
providers: [
30-
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(()=>DlDateTimeInputDirective), multi: true},
31-
{provide: NG_VALIDATORS, useExisting: forwardRef(()=>DlDateTimeInputDirective), multi: true}
30+
{provide: NG_VALUE_ACCESSOR, useExisting: DlDateTimeInputDirective, multi: true},
31+
{provide: NG_VALIDATORS, useExisting: DlDateTimeInputDirective, multi: true}
3232
]
3333
})
3434
export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Validator {
3535

3636
/* tslint:disable:member-ordering */
3737
private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
3838
// @ts-ignore
39-
return (this._inputFilter || ((value: any) => true))(this._value) ?
39+
return (this._inputFilter || (() => true))(this._value) ?
4040
null : {'dlDateTimeInputFilter': {'value': control.value}};
4141
}
4242
private _inputFilter: (value: (D | null)) => boolean = () => true;
@@ -48,7 +48,7 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
4848
private _changed: ((value: D) => void)[] = [];
4949
private _touched: (() => void)[] = [];
5050
private _validator = Validators.compose([this._parseValidator, this._filterValidator]);
51-
private _validatorOnChange: () => void = () => {};
51+
private _onValidatorChange: () => void = () => {};
5252
private _value: D | undefined = undefined;
5353

5454
/**
@@ -87,8 +87,8 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
8787
*/
8888
@Input()
8989
set dlDateTimeInputFilter(inputFilterFunction: (value: D | null) => boolean) {
90-
this._inputFilter = inputFilterFunction;
91-
this._validatorOnChange();
90+
this._inputFilter = inputFilterFunction||(()=>true);
91+
this._onValidatorChange();
9292
}
9393

9494
/* tslint:enable:member-ordering */
@@ -100,6 +100,19 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
100100
return this._value;
101101
}
102102

103+
/**
104+
* Set the value of the date/time input to a value of `D` | `undefined` | `null`;
105+
* @param newValue
106+
* the new value of the date/time input
107+
*/
108+
109+
set value(newValue: D | null | undefined) {
110+
if (newValue !== this._value) {
111+
this._value = newValue;
112+
this._changed.forEach(onChanged => onChanged(this._value));
113+
}
114+
}
115+
103116
/**
104117
* Emit a `change` event when the value of the input changes.
105118
*/
@@ -112,7 +125,7 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
112125
*/
113126
@HostListener('blur') _onBlur() {
114127
if (this._value) {
115-
this.writeValue(this._value);
128+
this._setElementValue(this._value);
116129
}
117130
this._touched.forEach(onTouched => onTouched());
118131
}
@@ -129,8 +142,16 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
129142
: moment(value, this._inputFormats, true);
130143

131144
this._isValid = testDate && testDate.isValid();
132-
this._value = this._isValid ? this._dateAdapter.fromMilliseconds(testDate.valueOf()) : undefined;
133-
this._changed.forEach(onChanged => onChanged(this._value));
145+
this.value = this._isValid ? this._dateAdapter.fromMilliseconds(testDate.valueOf()) : undefined;
146+
}
147+
148+
/**
149+
* @internal
150+
*/
151+
private _setElementValue(value: D) {
152+
if (value !== null && value !== undefined) {
153+
this._renderer.setProperty(this._elementRef.nativeElement, 'value', moment(value).format(this._displayFormat));
154+
}
134155
}
135156

136157
/**
@@ -151,7 +172,7 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
151172
* @internal
152173
*/
153174
registerOnValidatorChange(validatorOnChange: () => void): void {
154-
this._validatorOnChange = validatorOnChange;
175+
this._onValidatorChange = validatorOnChange;
155176
}
156177

157178
/**
@@ -172,9 +193,7 @@ export class DlDateTimeInputDirective<D> implements ControlValueAccessor, Valida
172193
* @internal
173194
*/
174195
writeValue(value: D): void {
175-
const normalizedValue = value === null || value === undefined
176-
? ''
177-
: moment(value).format(this._displayFormat);
178-
this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
196+
this.value = value;
197+
this._setElementValue(value);
179198
}
180199
}

‎src/lib/dl-date-time-input/specs/dl-date-time-input.directive.spec.ts‎

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component, DebugElement, ViewChild} from '@angular/core';
2-
import {async, ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
2+
import {async, ComponentFixture, fakeAsync, flush, TestBed,tick} from '@angular/core/testing';
33
import {FormsModule, NgForm} from '@angular/forms';
44
import {By} from '@angular/platform-browser';
55
import * as _moment from 'moment';
@@ -24,7 +24,7 @@ if ('default' in _moment) {
2424
</form>`
2525
})
2626
class DateModelComponent {
27-
dateValue: any;
27+
dateValue: number;
2828
@ViewChild(DlDateTimeInputDirective, {static: false}) input: DlDateTimeInputDirective<number>;
2929
dateTimeFilter: (value: (number | null)) => boolean = () => true;
3030
}
@@ -71,7 +71,7 @@ describe('DlDateTimeInputDirective', () => {
7171
it('should be displayed using default format', fakeAsync(() => {
7272
const octoberFirst = moment('2018年10月01日');
7373
const expectedValue = octoberFirst.format(DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT);
74-
component.dateValue = octoberFirst.toDate();
74+
component.dateValue = octoberFirst.valueOf();
7575
fixture.detectChanges();
7676
flush();
7777
const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement;
@@ -107,7 +107,7 @@ describe('DlDateTimeInputDirective', () => {
107107
expect(inputElement.classList).toContain('ng-touched');
108108
});
109109

110-
it('should reformat the input value on blur', () => {
110+
it('should reformat the input value on blur', fakeAsync(() => {
111111
const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement;
112112

113113
inputElement.value = '1/1/2001';
@@ -120,19 +120,21 @@ describe('DlDateTimeInputDirective', () => {
120120
fixture.detectChanges();
121121

122122
expect(inputElement.value).toBe(moment('2001年01月01日').format(DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT));
123-
});
123+
}));
124124

125125
it('should not reformat invalid dates on blur', () => {
126126
const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement;
127127

128-
inputElement.value = 'very-valid-date';
128+
inputElement.value = 'very-invalid-date';
129129
inputElement.dispatchEvent(new Event('input'));
130130
fixture.detectChanges();
131131

132+
expect(inputElement.value).toBe('very-invalid-date');
133+
132134
inputElement.dispatchEvent(new Event('blur'));
133135
fixture.detectChanges();
134136

135-
expect(inputElement.value).toBe('very-valid-date');
137+
expect(inputElement.value).toBe('very-invalid-date');
136138
});
137139

138140
it('should consider empty input to be valid (for non-required inputs)', () => {
@@ -143,7 +145,7 @@ describe('DlDateTimeInputDirective', () => {
143145

144146
it('should add ng-invalid on invalid input', fakeAsync(() => {
145147
const novemberFirst = moment('2018年11月01日');
146-
component.dateValue = novemberFirst.toDate();
148+
component.dateValue = novemberFirst.valueOf();
147149
fixture.detectChanges();
148150
flush();
149151

@@ -186,10 +188,13 @@ describe('DlDateTimeInputDirective', () => {
186188
expect(inputElement.classList).toContain('ng-valid');
187189
});
188190

189-
it('should add ng-invalid for valid input of filtered date', () => {
190-
const filteredValue = moment('2018年10月29日T17:00').valueOf();
191+
it('should add ng-invalid for input of filtered out date', () => {
192+
const expectedErrorValue = moment('2018年10月29日T17:00').valueOf();
193+
194+
const allowedValue = moment('2019年10月29日T17:00').valueOf();
195+
191196
spyOn(component, 'dateTimeFilter').and.callFake((date: number) => {
192-
return date !==filteredValue;
197+
return date ===allowedValue;
193198
});
194199

195200
const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement;
@@ -201,9 +206,33 @@ describe('DlDateTimeInputDirective', () => {
201206

202207
const control = debugElement.children[0].injector.get(NgForm).control.get('dateValue');
203208
expect(control.hasError('dlDateTimeInputFilter')).toBe(true);
204-
expect(control.errors.dlDateTimeInputFilter.value).toBe(filteredValue.valueOf());
209+
const value = control.errors.dlDateTimeInputFilter.value;
210+
expect(value).toBe(expectedErrorValue);
205211
});
206212

213+
it('should remove ng-invalid when model is updated with valid date', fakeAsync(() => {
214+
const allowedValue = moment('2019年10月29日T17:00').valueOf();
215+
spyOn(component, 'dateTimeFilter').and.callFake((date: number) => {
216+
return date === allowedValue;
217+
});
218+
219+
const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement;
220+
inputElement.value = '10/29/2018 05:00 PM';
221+
inputElement.dispatchEvent(new Event('blur'));
222+
223+
fixture.detectChanges();
224+
225+
expect(inputElement.classList).toContain('ng-invalid');
226+
227+
component.dateValue = allowedValue;
228+
229+
fixture.detectChanges();
230+
tick();
231+
fixture.detectChanges();
232+
233+
expect(inputElement.classList).toContain('ng-valid');
234+
}));
235+
207236
it('should disable input when setDisabled is called', () => {
208237
const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement;
209238
expect(inputElement.disabled).toBe(false);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /