diff --git a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.html b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.html
index 61d376645..cff85a6a6 100644
--- a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.html
+++ b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.html
@@ -11,4 +11,10 @@
End is required.
+
+
+
+ An interval must have a start and end
+
+
diff --git a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts
index e02046ea3..07ba55fe3 100644
--- a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts
+++ b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts
@@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Interval, IntervalInputComponent } from './interval-input.component';
-import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { Component, DebugElement, OnInit, ViewChild } from '@angular/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@@ -37,6 +37,35 @@ class TestHostComponent implements OnInit {
}
}
+/**
+ * Test host component to simulate parent component.
+ */
+@Component({
+ template: `
+
+
+
+
+
`
+})
+class NoValueRequiredTestHostComponent implements OnInit {
+
+ @ViewChild('intervalInput') intervalInputComponent: IntervalInputComponent;
+
+ form: FormGroup;
+
+ constructor(private _fb: FormBuilder) {
+ }
+
+ ngOnInit(): void {
+
+ this.form = this._fb.group({
+ interval: new FormControl(null)
+ });
+
+ }
+}
+
describe('InvertalInputComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture;
@@ -121,4 +150,51 @@ describe('InvertalInputComponent', () => {
});
+ it('should mark the form\'s validity correctly', () => {
+ expect(testHostComponent.intervalInputComponent.valueRequiredValidator).toBe(true);
+ expect(testHostComponent.intervalInputComponent.form.valid).toBe(true);
+
+ testHostComponent.intervalInputComponent.startIntervalControl.setValue(null);
+
+ testHostComponent.intervalInputComponent._handleInput();
+
+ expect(testHostComponent.intervalInputComponent.form.valid).toBe(false);
+ });
+
+});
+
+describe('InvertalInputComponent', () => {
+ let testHostComponent: NoValueRequiredTestHostComponent;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, BrowserAnimationsModule],
+ declarations: [IntervalInputComponent, NoValueRequiredTestHostComponent]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(NoValueRequiredTestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ });
+
+ it('should recieve the propagated valueRequiredValidator from the parent component', () => {
+ expect(testHostComponent.intervalInputComponent.valueRequiredValidator).toBe(false);
+ });
+
+ it('should mark the form\'s validity correctly', () => {
+ expect(testHostComponent.intervalInputComponent.form.valid).toBe(true);
+
+ testHostComponent.intervalInputComponent.startIntervalControl.setValue(1);
+
+ testHostComponent.intervalInputComponent._handleInput();
+
+ expect(testHostComponent.intervalInputComponent.form.valid).toBe(false);
+ });
+
});
diff --git a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts
index afe537010..dc55ed513 100644
--- a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts
+++ b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.ts
@@ -1,6 +1,6 @@
-import { Component, DoCheck, ElementRef, HostBinding, Input, OnDestroy, Optional, Self } from '@angular/core';
+import { Component, DoCheck, ElementRef, HostBinding, Input, OnDestroy, OnInit, Optional, Self } from '@angular/core';
import { MatFormFieldControl } from '@angular/material/form-field';
-import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, FormGroupDirective, NgControl, NgForm, Validators } from '@angular/forms';
+import { AbstractControl, ControlValueAccessor, FormBuilder, FormControl, FormGroup, FormGroupDirective, NgControl, NgForm, ValidatorFn, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@@ -27,6 +27,17 @@ export class IntervalInputErrorStateMatcher implements ErrorStateMatcher {
}
}
+/** Interval must have a start and end of the same type, either both numbers or both null */
+export function startEndSameTypeValidator(otherInterval: FormControl): ValidatorFn {
+ return (control: AbstractControl): { [key: string]: any } | null => {
+
+ // valid if both start and end are null or have values
+ const invalid = !(control.value === null && otherInterval.value === null || control.value !== null && otherInterval.value !== null);
+
+ return invalid ? { 'startEndSameTypeRequired': { value: control.value } } : null;
+ };
+}
+
class MatInputBase {
constructor(public _defaultErrorStateMatcher: ErrorStateMatcher,
public _parentForm: NgForm,
@@ -43,7 +54,7 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
styleUrls: ['./interval-input.component.scss'],
providers: [{ provide: MatFormFieldControl, useExisting: IntervalInputComponent }]
})
-export class IntervalInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl, DoCheck, CanUpdateErrorState, OnDestroy {
+export class IntervalInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl, DoCheck, CanUpdateErrorState, OnDestroy, OnInit {
static nextId = 0;
form: FormGroup;
@@ -53,11 +64,16 @@ export class IntervalInputComponent extends _MatInputMixinBase implements Contro
errorState = false;
controlType = 'dsp-interval-input';
matcher = new IntervalInputErrorStateMatcher();
- onChange = (_: any) => { };
- onTouched = () => { };
+
+ startIntervalControl: FormControl;
+ endIntervalControl: FormControl;
@Input() intervalStartLabel = 'start';
@Input() intervalEndLabel = 'end';
+ @Input() valueRequiredValidator = true;
+
+ onChange = (_: any) => { };
+ onTouched = () => { };
get empty() {
const userInput = this.form.value;
@@ -127,6 +143,10 @@ export class IntervalInputComponent extends _MatInputMixinBase implements Contro
} else {
this.form.setValue({ start: null, end: null });
}
+
+ this.startIntervalControl.updateValueAndValidity();
+ this.endIntervalControl.updateValueAndValidity();
+
this.stateChanges.next();
}
@@ -142,9 +162,12 @@ export class IntervalInputComponent extends _MatInputMixinBase implements Contro
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
+ this.startIntervalControl = new FormControl(null);
+ this.endIntervalControl = new FormControl(null);
+
this.form = fb.group({
- start: [null, Validators.required],
- end: [null, Validators.required]
+ start: this.startIntervalControl,
+ end: this.endIntervalControl
});
_fm.monitor(_elRef.nativeElement, true).subscribe(origin => {
@@ -157,6 +180,19 @@ export class IntervalInputComponent extends _MatInputMixinBase implements Contro
}
}
+ ngOnInit() {
+ if (this.valueRequiredValidator) {
+ this.startIntervalControl.setValidators([Validators.required, startEndSameTypeValidator(this.endIntervalControl)]);
+ this.endIntervalControl.setValidators([Validators.required, startEndSameTypeValidator(this.startIntervalControl)]);
+ } else {
+ this.startIntervalControl.setValidators(startEndSameTypeValidator(this.endIntervalControl));
+ this.endIntervalControl.setValidators(startEndSameTypeValidator(this.startIntervalControl));
+ }
+
+ this.startIntervalControl.updateValueAndValidity();
+ this.endIntervalControl.updateValueAndValidity();
+ }
+
ngDoCheck() {
if (this.ngControl) {
this.updateErrorState();
@@ -190,6 +226,8 @@ export class IntervalInputComponent extends _MatInputMixinBase implements Contro
}
_handleInput(): void {
+ this.startIntervalControl.updateValueAndValidity();
+ this.endIntervalControl.updateValueAndValidity();
this.onChange(this.value);
}
diff --git a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.html b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.html
index 83bd20222..6021777b1 100644
--- a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.html
+++ b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.html
@@ -6,7 +6,7 @@
-
+
New value must be different than the current value.
diff --git a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.spec.ts b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.spec.ts
index 47678685d..c1862f7f7 100644
--- a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.spec.ts
+++ b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-value.component.spec.ts
@@ -33,6 +33,7 @@ class TestIntervalInputComponent implements ControlValueAccessor, MatFormFieldCo
@Input() required: boolean;
@Input() shouldLabelFloat: boolean;
@Input() errorStateMatcher: ErrorStateMatcher;
+ @Input() valueRequiredValidator = true;
errorState = false;
focused = false;
@@ -113,12 +114,37 @@ class TestHostCreateValueComponent implements OnInit {
}
}
+/**
+ * Test host component to simulate parent component.
+ */
+@Component({
+ template: `
+ `
+})
+class TestHostCreateValueNoValueRequiredComponent implements OnInit {
+
+ @ViewChild('inputVal') inputValueComponent: IntervalValueComponent;
+
+ mode: 'read' | 'update' | 'create' | 'search';
+
+ ngOnInit() {
+
+ this.mode = 'create';
+ }
+}
+
describe('IntervalValueComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [IntervalValueComponent, TestHostDisplayValueComponent, TestIntervalInputComponent, TestHostCreateValueComponent],
+ declarations: [
+ IntervalValueComponent,
+ TestHostDisplayValueComponent,
+ TestIntervalInputComponent,
+ TestHostCreateValueComponent,
+ TestHostCreateValueNoValueRequiredComponent
+ ],
imports: [
ReactiveFormsModule,
MatInputModule,
@@ -456,4 +482,28 @@ describe('IntervalValueComponent', () => {
});
});
+
+ describe('create an interval value no value required', () => {
+
+ let testHostComponent: TestHostCreateValueNoValueRequiredComponent;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostCreateValueNoValueRequiredComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ });
+
+ it('should not create an empty value', () => {
+ expect(testHostComponent.inputValueComponent.getNewValue()).toBe(false);
+ expect(testHostComponent.inputValueComponent.form.valid).toBe(true);
+ });
+
+ it('should propagate valueRequiredValidator to child component', () => {
+ expect(testHostComponent.inputValueComponent.valueRequiredValidator).toBe(false);
+ });
+
+ });
});