Skip to content

Commit 954d5ed

Browse files
committed
feat(array): add array controls validation
1 parent baf5e1a commit 954d5ed

File tree

6 files changed

+176
-131
lines changed

6 files changed

+176
-131
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {AfterViewInit, Component, ContentChild, ElementRef} from '@angular/core';
2+
import {ControlContainer, FormArrayName} from '@angular/forms';
3+
import {FormValidationContainer} from './form-validation-container';
4+
5+
@Component({
6+
// tslint:disable:component-selector
7+
selector: '[formArrayContainer], form-array-container',
8+
template: `
9+
<ng-content></ng-content>
10+
<ng-container #errorsContainer></ng-container>
11+
`
12+
})
13+
export class FormArrayContainerComponent extends FormValidationContainer implements AfterViewInit {
14+
15+
// tslint:disable-next-line:variable-name
16+
@ContentChild(FormArrayName) _formControl: FormArrayName;
17+
18+
// tslint:disable-next-line:variable-name
19+
@ContentChild(FormArrayName, {read: ElementRef}) _el: ElementRef;
20+
21+
get formControl(): ControlContainer {
22+
return this._formControl;
23+
}
24+
25+
get el(): ElementRef<any> {
26+
return this._el;
27+
}
28+
}
Lines changed: 14 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,28 @@
1-
import {
2-
AfterContentInit,
3-
AfterViewInit,
4-
Component,
5-
ComponentFactoryResolver,
6-
ComponentRef,
7-
ContentChild,
8-
ElementRef,
9-
HostBinding,
10-
Inject,
11-
Input,
12-
Renderer2,
13-
ViewChild,
14-
ViewContainerRef
15-
} from '@angular/core';
16-
import {FormControlName} from '@angular/forms';
17-
import {TranslateService} from '@ngx-translate/core';
18-
import {VALIDATION_ERROR_CONFIG, ValidationErrorsConfig} from './error-validation-config';
19-
20-
function toScreamingSnakeCase(input: string): string {
21-
return input.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase();
22-
}
1+
import {AfterViewInit, Component, ContentChild, ElementRef} from '@angular/core';
2+
import {ControlContainer, FormControlName} from '@angular/forms';
3+
import {FormValidationContainer} from './form-validation-container';
234

245
@Component({
256
// tslint:disable:component-selector
267
selector: '[formFieldContainer], form-field-container',
278
template: `
28-
<ng-content></ng-content>
29-
<ng-container #errorsContainer></ng-container>
9+
<ng-content></ng-content>
10+
<ng-container #errorsContainer></ng-container>
3011
`
3112
})
32-
// <input-errors [innerValidationError]="innerValidationError" [messages]="messages" [params]="messageParams"></input-errors>
33-
export class FormFieldContainerComponent implements AfterViewInit {
34-
35-
@ContentChild(FormControlName) formControl: FormControlName;
36-
37-
@ContentChild(FormControlName, {read: ElementRef}) input: ElementRef;
38-
39-
@Input() customErrorMessages: {} = {};
40-
@Input() messageParams: {} = {};
41-
@Input() validationDisabled = false;
42-
@Input() innerValidationError: boolean;
43-
44-
@ViewChild('errorsContainer', {read: ViewContainerRef}) errorsContainer: ViewContainerRef;
13+
export class FormFieldContainerComponent extends FormValidationContainer implements AfterViewInit {
4514

46-
public messages: string[];
47-
private validationContext;
48-
private componentRef: ComponentRef<any>;
49-
50-
constructor(
51-
private elRef: ElementRef,
52-
private renderer: Renderer2,
53-
private translateService: TranslateService,
54-
private componentFactoryResolver: ComponentFactoryResolver,
55-
@Inject(VALIDATION_ERROR_CONFIG) private validationErrorsConfig: ValidationErrorsConfig) {
56-
this.validationContext = validationErrorsConfig.defaultContext;
57-
}
58-
59-
ngAfterViewInit(): void {
60-
this.addErrorComponent();
61-
this.updateErrorComponent();
62-
}
63-
64-
addErrorComponent() {
65-
if (this.errorsContainer && !this.componentRef) {
66-
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.validationErrorsConfig.errorComponent as any);
67-
this.componentRef = this.errorsContainer.createComponent(componentFactory);
68-
}
69-
}
15+
// tslint:disable-next-line:variable-name
16+
@ContentChild(FormControlName) _formControl: FormControlName;
7017

71-
updateErrorComponent() {
72-
this.addErrorComponent();
73-
74-
if (this.componentRef) {
75-
this.componentRef.instance.innerValidationError = this.innerValidationError;
76-
this.componentRef.instance.messages = this.messages;
77-
this.componentRef.instance.params = this.messageParams;
78-
this.componentRef.changeDetectorRef.detectChanges();
79-
}
80-
}
81-
82-
@HostBinding('class.has-error')
83-
get hasErrors(): boolean {
84-
const hasError = (!this.formControl.valid && this.formControl.dirty && this.formControl.touched) && !this.validationDisabled;
85-
86-
if (hasError && this.input && this.input.nativeElement) {
87-
this.messages = Object.keys(this.formControl.errors).map(error => {
88-
const fieldName = this.formControl.name;
89-
const errorKey = `${toScreamingSnakeCase(fieldName)}.ERRORS.${toScreamingSnakeCase(error)}`;
90-
if (this.translateService.instant(`${this.validationContext}.${errorKey}`) === `${this.validationContext}.${errorKey}`) {
91-
return `${this.validationErrorsConfig.defaultContext}.ERRORS.${toScreamingSnakeCase(error)}`;
92-
} else {
93-
return `${this.validationContext}.${errorKey}`;
94-
}
95-
});
96-
const params = Object.values(this.formControl.errors).reduce((a, b) => {
97-
a = {...a, ...b};
98-
return a;
99-
}, {});
100-
this.messageParams = this.messageParams ? {...this.messageParams, ...params} : params;
101-
if (this.messages && this.messages.length > 0) {
102-
this.messages = [this.messages[0]];
103-
}
104-
try {
105-
this.renderer.removeClass(this.input.nativeElement, 'is-valid');
106-
107-
} catch (e) {
108-
}
109-
this.renderer.addClass(this.input.nativeElement, 'is-invalid');
110-
111-
}
112-
this.updateErrorComponent();
113-
114-
return hasError;
115-
}
116-
117-
@HostBinding('class.has-success')
118-
get hasSuccess(): boolean {
119-
const hasSuccess = (
120-
this.formControl.valid &&
121-
this.formControl.dirty && this.formControl.touched) &&
122-
!this.validationDisabled;
123-
if (hasSuccess && this.input && this.input.nativeElement) {
124-
this.messages = [];
125-
try {
126-
this.renderer.removeClass(this.input.nativeElement, 'is-invalid');
127-
128-
} catch (e) {
129-
}
130-
}
131-
return;
132-
}
18+
// tslint:disable-next-line:variable-name
19+
@ContentChild(FormControlName, {read: ElementRef}) _input: ElementRef;
13320

134-
public setValidationContext(context: string): void {
135-
this.validationContext = context;
21+
get formControl(): ControlContainer {
22+
return this._formControl;
13623
}
13724

138-
setInnerValidation(innerValidation: boolean): void {
139-
this.innerValidationError = innerValidation;
25+
get el(): ElementRef<any> {
26+
return this._input;
14027
}
14128
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import {AfterViewInit, ComponentFactoryResolver, ComponentRef, ElementRef, HostBinding, Inject, Input, Renderer2, ViewChild, ViewContainerRef} from '@angular/core';
2+
import {TranslateService} from '@ngx-translate/core';
3+
import {VALIDATION_ERROR_CONFIG, ValidationErrorsConfig} from './error-validation-config';
4+
import {toScreamingSnakeCase} from './utils';
5+
import {ControlContainer} from '@angular/forms';
6+
7+
export abstract class FormValidationContainer implements AfterViewInit {
8+
9+
10+
@Input() customErrorMessages: {} = {};
11+
@Input() messageParams: {} = {};
12+
@Input() validationDisabled = false;
13+
@Input() innerValidationError: boolean;
14+
15+
@ViewChild('errorsContainer', {read: ViewContainerRef}) errorsContainer: ViewContainerRef;
16+
17+
public messages: string[];
18+
private validationContext;
19+
private componentRef: ComponentRef<any>;
20+
21+
constructor(
22+
private elRef: ElementRef,
23+
private renderer: Renderer2,
24+
private translateService: TranslateService,
25+
private componentFactoryResolver: ComponentFactoryResolver,
26+
@Inject(VALIDATION_ERROR_CONFIG) private validationErrorsConfig: ValidationErrorsConfig) {
27+
this.validationContext = validationErrorsConfig.defaultContext;
28+
}
29+
30+
ngAfterViewInit(): void {
31+
this.addErrorComponent();
32+
this.updateErrorComponent();
33+
}
34+
35+
addErrorComponent() {
36+
if (this.errorsContainer && !this.componentRef) {
37+
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.validationErrorsConfig.errorComponent as any);
38+
this.componentRef = this.errorsContainer.createComponent(componentFactory);
39+
}
40+
}
41+
42+
updateErrorComponent() {
43+
this.addErrorComponent();
44+
45+
if (this.componentRef) {
46+
this.componentRef.instance.innerValidationError = this.innerValidationError;
47+
this.componentRef.instance.messages = this.messages;
48+
this.componentRef.instance.params = this.messageParams;
49+
this.componentRef.changeDetectorRef.detectChanges();
50+
}
51+
}
52+
53+
@HostBinding('class.has-error')
54+
get hasErrors(): boolean {
55+
const hasError = (!this.formControl.valid && this.formControl.dirty && this.formControl.touched) && !this.validationDisabled;
56+
57+
if (hasError && this.el && this.el.nativeElement) {
58+
this.messages = Object.keys(this.formControl.errors).map(error => {
59+
const fieldName = this.formControl.name;
60+
const errorKey = `${toScreamingSnakeCase(fieldName)}.ERRORS.${toScreamingSnakeCase(error)}`;
61+
if (this.translateService.instant(`${this.validationContext}.${errorKey}`) === `${this.validationContext}.${errorKey}`) {
62+
return `${this.validationErrorsConfig.defaultContext}.ERRORS.${toScreamingSnakeCase(error)}`;
63+
} else {
64+
return `${this.validationContext}.${errorKey}`;
65+
}
66+
});
67+
const params = Object.values(this.formControl.errors).reduce((a, b) => {
68+
a = {...a, ...b};
69+
return a;
70+
}, {});
71+
this.messageParams = this.messageParams ? {...this.messageParams, ...params} : params;
72+
if (this.messages && this.messages.length > 0) {
73+
this.messages = [this.messages[0]];
74+
}
75+
try {
76+
this.renderer.removeClass(this.el.nativeElement, 'is-valid');
77+
78+
} catch (e) {
79+
}
80+
this.renderer.addClass(this.el.nativeElement, 'is-invalid');
81+
82+
}
83+
this.updateErrorComponent();
84+
85+
return hasError;
86+
}
87+
88+
@HostBinding('class.has-success')
89+
get hasSuccess(): boolean {
90+
const hasSuccess = (
91+
this.formControl.valid &&
92+
this.formControl.dirty && this.formControl.touched) &&
93+
!this.validationDisabled;
94+
if (hasSuccess && this.el && this.el.nativeElement) {
95+
this.messages = [];
96+
try {
97+
this.renderer.removeClass(this.el.nativeElement, 'is-invalid');
98+
99+
} catch (e) {
100+
}
101+
}
102+
return;
103+
}
104+
105+
public setValidationContext(context: string): void {
106+
this.validationContext = context;
107+
}
108+
109+
setInnerValidation(innerValidation: boolean): void {
110+
this.innerValidationError = innerValidation;
111+
}
112+
abstract get formControl(): ControlContainer;
113+
114+
abstract get el(): ElementRef;
115+
116+
}

projects/xtream/ngx-validation-errors/src/lib/ngx-validation-errors.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import {VALIDATION_ERROR_CONFIG, ValidationErrorsConfig} from './error-validatio
66
import {CommonModule} from '@angular/common';
77
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
88
import {TranslateModule} from '@ngx-translate/core';
9+
import {FormArrayContainerComponent} from './form-array-container.component';
910

1011

1112
export const defaultConfig = {
12-
defaultContext: 'general',
13+
defaultContext: 'GENERAL',
1314
errorComponent: InputErrorsComponent as any
1415
} as ValidationErrorsConfig;
1516

@@ -34,6 +35,7 @@ export function configFactory(customConfig: ValidationErrorsConfig, currentConfi
3435
declarations: [
3536
InputErrorsComponent,
3637
FormFieldContainerComponent,
38+
FormArrayContainerComponent,
3739
ValidationContextComponent
3840
],
3941
imports: [
@@ -45,6 +47,7 @@ export function configFactory(customConfig: ValidationErrorsConfig, currentConfi
4547
exports: [
4648
InputErrorsComponent,
4749
FormFieldContainerComponent,
50+
FormArrayContainerComponent,
4851
ValidationContextComponent
4952
],
5053
entryComponents: [
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function toScreamingSnakeCase(input: string): string {
2+
return input.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase();
3+
}

projects/xtream/ngx-validation-errors/src/lib/validation-context.component.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {AfterContentInit, Component, ContentChildren, Input, QueryList} from '@angular/core';
22
import {FormFieldContainerComponent} from './form-field-container.component';
3+
import {FormArrayContainerComponent} from './form-array-container.component';
34

45
@Component({
56
// tslint:disable:component-selector
@@ -8,15 +9,22 @@ import {FormFieldContainerComponent} from './form-field-container.component';
89
})
910
export class ValidationContextComponent implements AfterContentInit {
1011

11-
@ContentChildren(FormFieldContainerComponent, {descendants: true}) validators: QueryList<FormFieldContainerComponent>;
12+
@ContentChildren(FormFieldContainerComponent, {descendants: true}) fieldValidators: QueryList<FormFieldContainerComponent>;
13+
@ContentChildren(FormArrayContainerComponent, {descendants: true}) arrayValidators: QueryList<FormArrayContainerComponent>;
1214

1315
// tslint:disable:no-input-rename
1416
@Input() validationContext: string;
1517
@Input() innerValidationError: boolean;
1618

1719
ngAfterContentInit(): void {
18-
if (this.validators) {
19-
this.validators.forEach(i => {
20+
if (this.fieldValidators) {
21+
this.fieldValidators.forEach(i => {
22+
i.setValidationContext(this.validationContext);
23+
i.setInnerValidation(this.innerValidationError);
24+
});
25+
}
26+
if (this.arrayValidators) {
27+
this.arrayValidators.forEach(i => {
2028
i.setValidationContext(this.validationContext);
2129
i.setInnerValidation(this.innerValidationError);
2230
});

0 commit comments

Comments
 (0)