Skip to content

Commit e115c23

Browse files
authored
feat(input-group): add validation samples (#3198)
* feat(input-group): add validation samples * chore(input-group): refactor validation samples * chore(*): add configurations for live editing * chore(*): address comments * chore(*): enabling sourceMaps on dev and fix polyf * chore(*): refactor custom validators sample * chore(*): implement error obj interface * chore(*): refactor validation samples * refactor(input-group): invert visibility condition
1 parent d0b479a commit e115c23

15 files changed

Lines changed: 437 additions & 10 deletions

angular.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"buildOptimizer": false,
3030
"optimization": false,
3131
"namedChunks": true,
32-
"sourceMap": false,
32+
"sourceMap": true,
3333
"progress": true,
3434
"stylePreprocessorOptions": {
3535
"includePaths": ["node_modules"]

live-editing/configs/InputGroupConfigGenerator.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,48 @@ export class InputGroupConfigGenerator implements IConfigGenerator {
100100
shortenComponentPathBy: "/data-entries/"
101101
}));
102102

103+
// template-driven form validation
104+
configs.push(new Config({
105+
component: 'TemplateDrivenFormValidationComponent',
106+
additionalFiles: [
107+
"/src/app/data-entries/input-group/base-input.component.ts"
108+
],
109+
appModuleConfig: new AppModuleConfig({
110+
imports: ['IgxInputGroupModule', 'IgxButtonModule', 'IgxRippleModule', 'TemplateDrivenFormValidationComponent'],
111+
ngDeclarations: ['TemplateDrivenFormValidationComponent'],
112+
ngImports: ['IgxInputGroupModule', 'IgxButtonModule', 'IgxRippleModule']
113+
}),
114+
shortenComponentPathBy: "/data-entries/"
115+
}));
116+
117+
// reactive form validation
118+
configs.push(new Config({
119+
component: 'ReactiveFormValidationComponent',
120+
additionalFiles: [
121+
"/src/app/data-entries/input-group/base-input.component.ts"
122+
],
123+
appModuleConfig: new AppModuleConfig({
124+
imports: ['IgxInputGroupModule', 'IgxButtonModule', 'IgxRippleModule', 'ReactiveFormsModule', 'ReactiveFormValidationComponent'],
125+
ngDeclarations: ['ReactiveFormValidationComponent'],
126+
ngImports: ['IgxInputGroupModule', 'IgxButtonModule', 'IgxRippleModule', 'ReactiveFormsModule']
127+
}),
128+
shortenComponentPathBy: "/data-entries/"
129+
}));
130+
131+
// reactive form custom validation
132+
configs.push(new Config({
133+
component: 'ReactiveFormCustomValidationComponent',
134+
additionalFiles: [
135+
"/src/app/data-entries/input-group/base-input.component.ts"
136+
],
137+
appModuleConfig: new AppModuleConfig({
138+
imports: ['IgxInputGroupModule', 'IgxButtonModule', 'IgxRippleModule', 'ReactiveFormsModule', 'ReactiveFormCustomValidationComponent'],
139+
ngDeclarations: ['ReactiveFormCustomValidationComponent'],
140+
ngImports: ['IgxInputGroupModule', 'IgxButtonModule', 'IgxRippleModule', 'ReactiveFormsModule']
141+
}),
142+
shortenComponentPathBy: "/data-entries/"
143+
}));
144+
103145
return configs;
104146
}
105147
}

src/app/data-entries/data-entries-routes-data.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,8 @@ export const dataEntriesRoutesData = {
6060
"select-input-directives": { displayName: "Select Input Directives", parentName: "Select" },
6161
"select-header-footer": { displayName: "Select with Header&Footer Templates", parentName: "Select" },
6262
"input-text-selection": { displayName: "Input with Text Selection", parentName: "Input Group" },
63-
"typed-form": { displayName: "Typed Form", parentName: "Input Group" }
63+
"typed-form": { displayName: "Typed Form", parentName: "Input Group" },
64+
"template-driven-form-validation": { displayName: "Template Driven Form Validation", parentName: "Input Group" },
65+
"reactive-form-validation": { displayName: "Reactive Form Validation", parentName: "Input Group" },
66+
"reactive-form-custom-validation": { displayName: "Reactive Form Custom Validation", parentName: "Input Group" }
6467
};

src/app/data-entries/data-entries-routing.module.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ import { SwitchSample1Component } from './switch/switch-sample-1/switch-sample-1
6161
import { SwitchSample2Component } from './switch/switch-sample-2/switch-sample-2.component';
6262
import { SwitchStylingComponent } from './switch/switch-styling/switch-styling.component';
6363
import { RatingInFormComponent } from './rating/rating-form/rating-form.component';
64+
import { TemplateDrivenFormValidationComponent } from './input-group/template-driven-form-validation/template-driven-form-validation.component';
65+
import { ReactiveFormValidationComponent } from './input-group/reactive-form-validation/reactive-form-validation.component';
66+
import { ReactiveFormCustomValidationComponent } from './input-group/reactive-form-custom-validation/reactive-form-custom-validation.component';
6467

6568
export const dataEntriesRoutes: Routes = [
6669
{
@@ -362,6 +365,21 @@ export const dataEntriesRoutes: Routes = [
362365
component: TypedFormComponent,
363366
data: dataEntriesRoutesData['typed-form'],
364367
path: 'typed-form'
368+
},
369+
{
370+
component: TemplateDrivenFormValidationComponent,
371+
data: dataEntriesRoutesData['template-driven-form-validation'],
372+
path: 'template-driven-form-validation'
373+
},
374+
{
375+
component: ReactiveFormValidationComponent,
376+
data: dataEntriesRoutesData['reactive-form-validation'],
377+
path: 'reactive-form-validation'
378+
},
379+
{
380+
component: ReactiveFormCustomValidationComponent,
381+
data: dataEntriesRoutesData['reactive-form-custom-validation'],
382+
path: 'reactive-form-custom-validation'
365383
}
366384
];
367385

src/app/data-entries/data-entries.module.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ import { SwitchSample2Component } from './switch/switch-sample-2/switch-sample-2
7070
import { SwitchStylingComponent } from './switch/switch-styling/switch-styling.component';
7171
import { TypedFormComponent } from './input-group/typed-form/typed-form.component';
7272
import { RatingInFormComponent } from './rating/rating-form/rating-form.component';
73+
import { TemplateDrivenFormValidationComponent } from './input-group/template-driven-form-validation/template-driven-form-validation.component';
74+
import { ReactiveFormValidationComponent } from './input-group/reactive-form-validation/reactive-form-validation.component';
75+
import { ReactiveFormCustomValidationComponent } from './input-group/reactive-form-custom-validation/reactive-form-custom-validation.component';
7376

7477
@NgModule({
7578
declarations: [
@@ -133,7 +136,10 @@ import { RatingInFormComponent } from './rating/rating-form/rating-form.componen
133136
ButtonsStyleComponent,
134137
ButtonGroupStyleComponent,
135138
InputTextSelectionComponent,
136-
TypedFormComponent
139+
TypedFormComponent,
140+
TemplateDrivenFormValidationComponent,
141+
ReactiveFormValidationComponent,
142+
ReactiveFormCustomValidationComponent
137143
],
138144
imports: [
139145
CommonModule,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<article>
2+
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
3+
<igx-input-group>
4+
<label igxLabel for="email">Email</label>
5+
<input igxInput name="email" type="email" formControlName="email" />
6+
<igx-hint *ngIf="email.errors?.email">Please enter a valid email address</igx-hint>
7+
<igx-hint *ngIf="!email.errors?.email && email.errors?.localPart">This email address is not allowed</igx-hint>
8+
<igx-hint *ngIf="!email.errors?.email && email.errors?.domain">This domain is not allowed</igx-hint>
9+
</igx-input-group>
10+
11+
<igx-input-group>
12+
<label igxLabel for="password">Password</label>
13+
<input igxInput name="password" [type]="showPassword ? 'text' : 'password'" formControlName="password" />
14+
<igx-hint *ngIf="password.errors?.containsEmail">Should not contain the email address</igx-hint>
15+
<igx-hint *ngIf="password.errors?.minlength">Should be at least 8 characters</igx-hint>
16+
<igx-hint *ngIf="password.errors?.pattern">Should contain a digit and a special character</igx-hint>
17+
<igx-icon *ngIf="password.value" igxSuffix (click)="showPassword = !showPassword">
18+
{{ togglePasswordVisibility }}
19+
</igx-icon>
20+
</igx-input-group>
21+
22+
<igx-input-group>
23+
<label igxLabel for="repeatPassword">Repeat password</label>
24+
<input igxInput name="repeatPassword" [type]="showRepeat ? 'text' : 'password'" formControlName="repeatPassword" />
25+
<igx-hint *ngIf="repeatPassword.errors?.mismatch
26+
&& !repeatPassword.pristine
27+
&& repeatPassword.value">
28+
Passwords do not match
29+
</igx-hint>
30+
<igx-icon *ngIf="repeatPassword.value" igxSuffix (click)="showRepeat = !showRepeat">
31+
{{ toggleRepeatVisibility }}
32+
</igx-icon>
33+
</igx-input-group>
34+
35+
<button igxButton="raised" igxRipple type="submit" [disabled]="!registrationForm.valid">Submit</button>
36+
</form>
37+
</article>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
article {
2+
max-width: 550px;
3+
padding: 16px;
4+
}
5+
6+
form {
7+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12);
8+
padding: 24px;
9+
margin-bottom: 48px;
10+
}
11+
12+
form > * {
13+
margin-top: 32px;
14+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Component } from '@angular/core';
2+
import { AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
3+
4+
interface User
5+
{
6+
email: FormControl<string>;
7+
password: FormControl<string>;
8+
repeatPassword: FormControl<string>;
9+
}
10+
11+
interface ValidatorErrors
12+
{
13+
localPart?: boolean;
14+
domain?: boolean;
15+
containsEmail?: boolean;
16+
mismatch?: boolean;
17+
}
18+
19+
@Component({
20+
selector: 'app-reactive-form-custom-validation',
21+
templateUrl: './reactive-form-custom-validation.component.html',
22+
styleUrls: ['./reactive-form-custom-validation.component.scss']
23+
})
24+
export class ReactiveFormCustomValidationComponent {
25+
private pattern = `^(?=.*[A-Za-z])(?=.*\\d)(?=.*[~!@?#+$"'%^&:;*\\-_=.,<>])[A-Za-z\\d~!@?#+$"'%^&:;*\\-_=.,<>]+$`;
26+
public registrationForm: FormGroup<User>;
27+
public showPassword: boolean = false;
28+
public showRepeat: boolean = false;
29+
30+
constructor(fb: FormBuilder) {
31+
this.registrationForm = fb.group({
32+
email: ['', {
33+
nonNullable: true,
34+
validators: [
35+
Validators.required,
36+
Validators.email,
37+
this.emailValidator('infragistics')
38+
]
39+
}],
40+
password: ['', {
41+
nonNullable: true,
42+
validators: [
43+
Validators.required,
44+
Validators.minLength(8),
45+
Validators.pattern(this.pattern)
46+
]
47+
}],
48+
repeatPassword: ['', {
49+
nonNullable: true,
50+
validators: [Validators.required]
51+
}]
52+
},
53+
{
54+
validators: [this.passwordValidator()]
55+
});
56+
}
57+
58+
public get email() {
59+
return this.registrationForm.get('email');
60+
}
61+
62+
public get password() {
63+
return this.registrationForm.get('password');
64+
}
65+
66+
public get repeatPassword() {
67+
return this.registrationForm.get('repeatPassword');
68+
}
69+
70+
public get togglePasswordVisibility() {
71+
return this.showPassword ? 'visibility_off' : 'visibility';
72+
}
73+
74+
public get toggleRepeatVisibility() {
75+
return this.showRepeat ? 'visibility_off' : 'visibility';
76+
}
77+
78+
public onSubmit() {
79+
console.log(this.registrationForm.value);
80+
this.registrationForm.reset();
81+
}
82+
83+
private emailValidator(val: string): ValidatorFn {
84+
return (control: AbstractControl): ValidationErrors | null => {
85+
const value = control.value?.toLowerCase();
86+
const localPartRegex = new RegExp(`(?<=(${val})).*[@]`);
87+
const domainRegex = new RegExp(`(?<=[@])(?=.*(${val}))`);
88+
const returnObj: ValidatorErrors = {};
89+
90+
if (value && localPartRegex.test(value)) {
91+
returnObj.localPart = true;
92+
}
93+
94+
if (value && domainRegex.test(value)) {
95+
returnObj.domain = true;
96+
}
97+
98+
return returnObj;
99+
}
100+
}
101+
102+
private passwordValidator(): ValidatorFn {
103+
return (control: AbstractControl): ValidationErrors | null => {
104+
const email = control.get('email');
105+
const password = control.get('password');
106+
const repeatPassword = control.get('repeatPassword');
107+
const returnObj: ValidatorErrors = {};
108+
109+
if (email.value
110+
&& password.value
111+
&& password.value.toLowerCase().includes(email.value)) {
112+
password.setErrors({ ...password.errors, containsEmail: true });
113+
returnObj.containsEmail = true;
114+
}
115+
116+
if (password
117+
&& repeatPassword
118+
&& password.value !== repeatPassword.value) {
119+
repeatPassword.setErrors({ ...repeatPassword.errors, mismatch: true });
120+
returnObj.mismatch = true;
121+
}
122+
123+
if (!returnObj.containsEmail && password.errors?.containsEmail) {
124+
password.setValue(password.value);
125+
}
126+
127+
if (!returnObj.mismatch && repeatPassword.errors?.mismatch) {
128+
repeatPassword.setValue(repeatPassword.value);
129+
}
130+
131+
return returnObj;
132+
}
133+
}
134+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<article>
2+
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
3+
<h4>Registration Form</h4>
4+
<div class="container">
5+
<igx-input-group>
6+
<label igxLabel for="username">Username</label>
7+
<input igxInput name="username" type="text" formControlName="username" />
8+
</igx-input-group>
9+
10+
<igx-input-group>
11+
<label igxLabel for="email">Email</label>
12+
<input igxInput name="email" type="email" formControlName="email" />
13+
<igx-hint *ngIf="email.errors?.email">Please enter a valid email</igx-hint>
14+
</igx-input-group>
15+
16+
<igx-input-group>
17+
<label igxLabel for="password">Password</label>
18+
<input igxInput name="password" [type]="showPassword ? 'text' : 'password'" formControlName="password" />
19+
<igx-hint *ngIf="password.errors?.minlength">Password should be at least 8 characters</igx-hint>
20+
<igx-icon *ngIf="password.value" igxSuffix (click)="showPassword = !showPassword">
21+
{{ togglePasswordVisibility }}
22+
</igx-icon>
23+
</igx-input-group>
24+
25+
<button igxButton="raised" igxRipple type="submit" [disabled]="!registrationForm.valid">Submit</button>
26+
</div>
27+
</form>
28+
</article>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
article {
2+
max-width: 550px;
3+
padding: 16px;
4+
}
5+
6+
.container > * {
7+
margin-top: 32px;
8+
}
9+
10+
form {
11+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12);
12+
padding: 24px;
13+
margin-bottom: 48px;
14+
}
15+
16+
h4 {
17+
margin-top: 0;
18+
}

0 commit comments

Comments
 (0)