Skip to content

Commit b27fe59

Browse files
SAndreevabkulov
authored andcommitted
igxInput - add ability to toggle required dynamically (#4361)
* feat(igxInput): toggle required dynamically #3722 * chore(*): ability to assign the directive to a template ref variable #3722
1 parent 0a41383 commit b27fe59

File tree

6 files changed

+237
-23
lines changed

6 files changed

+237
-23
lines changed

projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ describe('IgxInput', () => {
3333
RequiredTwoWayDataBoundInputComponent,
3434
DataBoundDisabledInputComponent,
3535
ReactiveFormComponent,
36-
InputsWithSameNameAttributesComponent
36+
InputsWithSameNameAttributesComponent,
37+
ToggleRequiredWithNgModelInputComponent
3738
],
3839
imports: [
3940
IgxInputGroupModule,
@@ -247,6 +248,7 @@ describe('IgxInput', () => {
247248
});
248249

249250
it('When updating two inputs with same attribute names through ngModel, label should responds', fakeAsync(() => {
251+
250252
const fix = TestBed.createComponent(InputsWithSameNameAttributesComponent);
251253
fix.detectChanges();
252254

@@ -319,6 +321,116 @@ describe('IgxInput', () => {
319321
fix.detectChanges();
320322
expect(firstInputGroup.nativeElement.classList.contains('igx-input-group--invalid')).toBe(true);
321323
}));
324+
325+
it('Should style input when required is toggled dynamically.', () => {
326+
const fixture = TestBed.createComponent(ToggleRequiredWithNgModelInputComponent);
327+
fixture.detectChanges();
328+
329+
const instance = fixture.componentInstance;
330+
const input = instance.igxInputs.toArray()[1];
331+
const inputGroup = instance.igxInputGroups.toArray()[1];
332+
333+
expect(input.required).toBe(false);
334+
expect(inputGroup.isRequired).toBeFalsy();
335+
expect(input.valid).toBe(IgxInputState.INITIAL);
336+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(false);
337+
338+
dispatchInputEvent('focus', input.nativeElement, fixture);
339+
expect(input.valid).toBe(IgxInputState.INITIAL);
340+
341+
input.value = '123';
342+
dispatchInputEvent('input', input.nativeElement, fixture);
343+
expect(input.valid).toBe(IgxInputState.INITIAL);
344+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
345+
346+
dispatchInputEvent('blur', input.nativeElement, fixture);
347+
expect(input.valid).toBe(IgxInputState.INITIAL);
348+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
349+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
350+
351+
instance.isRequired = true;
352+
fixture.detectChanges();
353+
354+
expect(input.required).toBe(true);
355+
356+
expect(inputGroup.isRequired).toBeTruthy();
357+
expect(input.valid).toBe(IgxInputState.INITIAL);
358+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
359+
360+
dispatchInputEvent('focus', input.nativeElement, fixture);
361+
expect(input.valid).toBe(IgxInputState.INITIAL);
362+
363+
input.value = '';
364+
dispatchInputEvent('input', input.nativeElement, fixture);
365+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
366+
367+
dispatchInputEvent('blur', input.nativeElement, fixture);
368+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
369+
370+
input.value = '123';
371+
dispatchInputEvent('input', input.nativeElement, fixture);
372+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true);
373+
374+
dispatchInputEvent('blur', input.nativeElement, fixture);
375+
expect(input.valid).toBe(IgxInputState.INITIAL);
376+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
377+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
378+
});
379+
380+
it('Should style input with ngModel when required is toggled dynamically.', () => {
381+
const fixture = TestBed.createComponent(ToggleRequiredWithNgModelInputComponent);
382+
fixture.detectChanges();
383+
384+
const instance = fixture.componentInstance;
385+
const input = instance.igxInputs.toArray()[0];
386+
const inputGroup = instance.igxInputGroups.toArray()[0];
387+
388+
expect(input.required).toBe(false);
389+
expect(inputGroup.isRequired).toBeFalsy();
390+
expect(input.valid).toBe(IgxInputState.INITIAL);
391+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(false);
392+
393+
dispatchInputEvent('focus', input.nativeElement, fixture);
394+
expect(input.valid).toBe(IgxInputState.INITIAL);
395+
396+
input.value = '123';
397+
dispatchInputEvent('input', input.nativeElement, fixture);
398+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true);
399+
400+
dispatchInputEvent('blur', input.nativeElement, fixture);
401+
expect(input.valid).toBe(IgxInputState.INITIAL);
402+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
403+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
404+
405+
instance.isRequired = true;
406+
fixture.detectChanges();
407+
408+
expect(input.required).toBe(true);
409+
410+
expect(inputGroup.isRequired).toBeTruthy();
411+
expect(input.valid).toBe(IgxInputState.INITIAL);
412+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
413+
414+
dispatchInputEvent('focus', input.nativeElement, fixture);
415+
expect(input.valid).toBe(IgxInputState.INITIAL);
416+
417+
input.value = '';
418+
dispatchInputEvent('input', input.nativeElement, fixture);
419+
dispatchInputEvent('blur', input.nativeElement, fixture);
420+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
421+
422+
dispatchInputEvent('focus', input.nativeElement, fixture);
423+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
424+
425+
input.value = '123';
426+
dispatchInputEvent('input', input.nativeElement, fixture);
427+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true);
428+
429+
dispatchInputEvent('blur', input.nativeElement, fixture);
430+
expect(input.valid).toBe(IgxInputState.INITIAL);
431+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
432+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
433+
});
322434
});
323435

324436
@Component({ template: `
@@ -529,6 +641,26 @@ class ReactiveFormComponent {
529641
}
530642
}
531643

644+
@Component({ template: `<igx-input-group>
645+
<label for="test" igxLabel>Test</label>
646+
<input name="test" type="text" igxInput [(ngModel)]="data" [required]="isRequired"/>
647+
</igx-input-group>
648+
<igx-input-group>
649+
<label for="test" igxLabel>Test</label>
650+
<input name="test" type="text" igxInput [value]="data1" [required]="isRequired"/>
651+
</igx-input-group>` })
652+
class ToggleRequiredWithNgModelInputComponent {
653+
@ViewChildren(IgxInputDirective)
654+
public igxInputs: QueryList<IgxInputDirective>;
655+
656+
@ViewChildren(IgxInputGroupComponent)
657+
public igxInputGroups: QueryList<IgxInputGroupComponent>;
658+
659+
public data = '';
660+
public data1 = '';
661+
public isRequired = false;
662+
}
663+
532664
function testRequiredValidation(inputElement, fixture) {
533665
dispatchInputEvent('focus', inputElement, fixture);
534666
inputElement.value = 'test';

projects/igniteui-angular/src/lib/directives/input/input.directive.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export enum IgxInputState {
2424
}
2525

2626
@Directive({
27-
selector: '[igxInput]'
27+
selector: '[igxInput]',
28+
exportAs: 'igxInput'
2829
})
2930
export class IgxInputDirective implements AfterViewInit, OnDestroy {
3031
private _valid = IgxInputState.INITIAL;
@@ -92,6 +93,40 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
9293
public get disabled() {
9394
return this.nativeElement.hasAttribute('disabled');
9495
}
96+
97+
/**
98+
* Sets the `required` property.
99+
* ```html
100+
* <input-group>
101+
* <input igxInput #igxInput [required]="true">
102+
* </input-group>
103+
* ```
104+
* @memberof IgxInputDirective
105+
*/
106+
@Input()
107+
public set required(value: boolean) {
108+
if (typeof value === 'boolean') {
109+
this.nativeElement.required = this.inputGroup.isRequired = value;
110+
111+
if (value && !this.nativeElement.checkValidity()) {
112+
this._valid = IgxInputState.INVALID;
113+
} else {
114+
this._valid = IgxInputState.INITIAL;
115+
}
116+
}
117+
}
118+
119+
/**
120+
* Gets whether the igxInput is required.
121+
* ```typescript
122+
* let isRequired = this.igxInput.required;
123+
* ```
124+
* @memberof IgxInputDirective
125+
*/
126+
public get required() {
127+
return this.nativeElement.hasAttribute('required');
128+
}
129+
95130
/**
96131
* Sets/gets whether the `"igx-input-group__input"` class is added to the host element.
97132
* Default value is `false`.
@@ -227,16 +262,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
227262
}
228263
}
229264
}
230-
/**
231-
* Gets whether the igxInput is required.
232-
* ```typescript
233-
* let isRequired = this.igxInput.required;
234-
* ```
235-
* @memberof IgxInputDirective
236-
*/
237-
public get required() {
238-
return this.nativeElement.hasAttribute('required');
239-
}
240265
/**
241266
* Gets whether the igxInput has a placeholder.
242267
* ```typescript
@@ -287,6 +312,18 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
287312
public get valid(): IgxInputState {
288313
return this._valid;
289314
}
315+
316+
/**
317+
* Gets whether the igxInput is valid.
318+
* ```typescript
319+
* let valid = this.igxInput.isValid;
320+
* ```
321+
* @memberof IgxInputDirective
322+
*/
323+
public get isValid(): boolean {
324+
return this.valid !== IgxInputState.INVALID;
325+
}
326+
290327
/**
291328
* Sets the state of the igxInput.
292329
* ```typescript

src/app/input-group/input-group.sample.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
<div class="samples">
33
<input-group-child-sample></input-group-child-sample>
44

5+
<h3 class="sample-title">Line Style Input with required @Input</h3>
6+
<form class="form">
7+
<igx-input-group>
8+
<label igxLabel>Required</label>
9+
<input igxInput #myInput="igxInput" [value]="value" [required]="isRequired"/>
10+
<igx-hint *ngIf="!myInput.isValid">This is required</igx-hint>
11+
</igx-input-group>
12+
<button igxButton (click)="toggleRequired()">Toggle required</button>
13+
</form>
14+
515
<h3 class="sample-title">Line Style Input</h3>
616
<form class="form">
717
<!-- Basic text field -->

src/app/input-group/input-group.sample.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ export class InputGroupSampleComponent {
1111
firstName: 'Oke',
1212
lastName: 'Nduka'
1313
};
14+
15+
public isRequired = true;
16+
public value = '';
17+
18+
public toggleRequired() {
19+
this.isRequired = !this.isRequired;
20+
}
1421
}

src/app/time-picker/time-picker.sample.html

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<section class="sample-content">
66
<article class="timepicker-column">
77
<h4 class="sample-title">Time Picker with Dropdown</h4>
8-
<div class="sample-description">{{showDate()}}</div>
8+
<div class="sample-description">{{showDate(date)}}</div>
99
<div class="preview" style="width: 200px">
1010
<igx-time-picker #tp (onValueChanged)="valueChanged($event)"
1111
(onValidationFailed)="validationFailed($event)" [mode]="mode" [isSpinLoop]="isSpinLoop"
@@ -16,7 +16,6 @@ <h4 class="sample-title">Time Picker with Dropdown</h4>
1616
<article class="timepicker-column">
1717
<h4 class="sample-title">Horizontal Time Picker</h4>
1818
<p class="sample-description">AM/PM Time format</p>
19-
<p class="sample-description"> </p>
2019
<div class="preview">
2120
<igx-time-picker format="h:mm tt"></igx-time-picker>
2221
</div>
@@ -51,5 +50,27 @@ <h4 class="sample-title">Templated Time Picker</h4>
5150
</igx-time-picker>
5251
</div>
5352
</article>
53+
<article class="timepicker-column">
54+
<h4 class="sample-title">Templated Time Picker</h4>
55+
<p class="sample-description">Time picker with required input group</p>
56+
<div class="preview">
57+
<igx-time-picker #templatedTimePicker [value]="today">
58+
<ng-template igxTimePickerTemplate
59+
let-openDialog="openDialog"
60+
let-value="value">
61+
<igx-input-group [supressInputAutofocus]="true">
62+
<label igxLabel>Required</label>
63+
<igx-prefix (click)="openDialog()">
64+
<igx-icon>access_time</igx-icon>
65+
</igx-prefix>
66+
<input igxInput [value]="showDate(value)" [required]="isRequired"/>
67+
<igx-suffix igxRipple (click)="change()">
68+
<igx-icon fontSet="material">star</igx-icon>
69+
</igx-suffix>
70+
</igx-input-group>
71+
</ng-template>
72+
</igx-time-picker>
73+
</div>
74+
</article>
5475
</section>
5576
</div>

src/app/time-picker/time-picker.sample.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,29 @@ import { IgxTimePickerComponent, InteractionMode } from 'igniteui-angular';
77
templateUrl: 'time-picker.sample.html'
88
})
99
export class TimePickerSampleComponent {
10-
max = "19:00";
11-
min = "09:00";
10+
max = '19:00';
11+
min = '09:00';
1212

1313
itemsDelta = { hours: 1, minutes: 5 };
14-
format = "hh:mm tt";
14+
format = 'hh:mm tt';
1515
isSpinLoop = true;
1616
isVertical = true;
17-
public mode = InteractionMode.DropDown;
17+
mode = InteractionMode.DropDown;
1818

1919
date = new Date(2018, 10, 27, 17, 45, 0, 0);
20+
today = new Date(Date.now());
2021

21-
showDate() {
22-
return this.date ? this.date.toLocaleString() : 'Value is null.';
22+
isRequired = true;
23+
24+
@ViewChild('tp', { read: IgxTimePickerComponent })
25+
public tp: IgxTimePickerComponent;
26+
27+
showDate(date) {
28+
return date ? date.toLocaleString() : 'Value is null.';
29+
}
30+
31+
change() {
32+
this.isRequired = !this.isRequired;
2333
}
2434

2535
valueChanged(event) {
@@ -29,7 +39,4 @@ export class TimePickerSampleComponent {
2939
validationFailed(event) {
3040
console.log(event);
3141
}
32-
33-
@ViewChild('tp', { read: IgxTimePickerComponent })
34-
public tp: IgxTimePickerComponent;
3542
}

0 commit comments

Comments
 (0)