Skip to content

Commit 8e3e6f0

Browse files
Add displayValidationMessageWhen configuration
To be able to change when validation messages are shown, configuration is added.
1 parent 6dcf36d commit 8e3e6f0

File tree

7 files changed

+137
-33
lines changed

7 files changed

+137
-33
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export { ReactiveValidationModule } from './angular-reactive-validation.module';
1+
export { ReactiveValidationModule } from './reactive-validation.module';
2+
export { ReactiveValidationModuleConfiguration } from './reactive-validation-module-configuration';
23
export { Validators } from './validators';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { InjectionToken } from '@angular/core';
2+
import { ReactiveValidationModuleConfiguration } from './reactive-validation-module-configuration';
3+
4+
export const ReactiveValidationModuleConfigurationToken =
5+
new InjectionToken<ReactiveValidationModuleConfiguration>('ReactiveValidationModuleConfiguration');
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { FormControl } from '@angular/forms';
2+
3+
export interface ReactiveValidationModuleConfiguration {
4+
/**
5+
* Function to override the default validation message display behaviour with a different implementation.
6+
* The default returns true when the control is touched, or the form has been submitted.
7+
*
8+
* @param control The control for which to display errors
9+
* @param formSubmitted whether the form is submitted or not. When undefined, it's not known
10+
* if the form is submitted, due to the form tag missing a formGroup.
11+
*/
12+
displayValidationMessageWhen?: (control: FormControl, formSubmitted: boolean | undefined) => boolean;
13+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { ReactiveValidationModule } from './reactive-validation.module';
4+
import { ReactiveValidationModuleConfigurationToken } from './reactive-validation-module-configuration-token';
5+
6+
describe(`ReactiveValidationModule`, () => {
7+
describe(`when not calling forRoot`, () => {
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({
10+
imports: [ ReactiveValidationModule ]
11+
});
12+
});
13+
14+
it(`should not provide configuration`, () => {
15+
expect(() => TestBed.get(ReactiveValidationModuleConfigurationToken)).toThrowError(/No provider for/);
16+
});
17+
});
18+
19+
describe(`when calling forRoot`, () => {
20+
let configuration: any;
21+
22+
beforeEach(() => {
23+
configuration = { };
24+
TestBed.configureTestingModule({
25+
imports: [
26+
ReactiveValidationModule.forRoot(configuration)
27+
]
28+
});
29+
});
30+
31+
it(`should provide configuration`, () => {
32+
expect(TestBed.get(ReactiveValidationModuleConfigurationToken)).toEqual(configuration);
33+
});
34+
});
35+
});

angular-reactive-validation/src/angular-reactive-validation.module.ts renamed to angular-reactive-validation/src/reactive-validation.module.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { NgModule } from '@angular/core';
1+
import { NgModule, ModuleWithProviders } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33
import { ValidationMessagesComponent } from './validation-messages/validation-messages.component';
44
import { ValidationMessageComponent } from './validation-message/validation-message.component';
55
import { FormDirective } from './form/form.directive';
6+
import { ReactiveValidationModuleConfiguration } from './reactive-validation-module-configuration';
7+
import { ReactiveValidationModuleConfigurationToken } from './reactive-validation-module-configuration-token';
68

79
@NgModule({
810
imports: [
@@ -20,4 +22,12 @@ import { FormDirective } from './form/form.directive';
2022
]
2123
})
2224
export class ReactiveValidationModule {
25+
static forRoot(configuration?: ReactiveValidationModuleConfiguration): ModuleWithProviders {
26+
return {
27+
ngModule: ReactiveValidationModule,
28+
providers: [{
29+
provide: ReactiveValidationModuleConfigurationToken, useValue: configuration
30+
}]
31+
};
32+
}
2333
}

angular-reactive-validation/src/validation-messages/validation-messages.component.spec.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { Component } from '@angular/core';
66
import { ValidationMessageComponent } from '../validation-message/validation-message.component';
77
import { Validators } from '../validators';
88
import { Subject } from 'rxjs/Subject';
9+
import { ReactiveValidationModule } from '../reactive-validation.module';
10+
import { ReactiveValidationModuleConfiguration } from '../public_api';
911

1012
describe('ValidationMessagesComponent', () => {
1113
describe('properties and functions', () => {
@@ -62,22 +64,22 @@ describe('ValidationMessagesComponent', () => {
6264
component.for = [firstNameControl, 'middleName', lastNameControl];
6365
});
6466

65-
it(`isValid returns true when there are no controls with ValidationErrors and they are touched`, () => {
67+
it(`isValid returns true when there are no controls with ValidationErrors and they are touched (default configuration)`, () => {
6668
component.for = firstNameControl;
6769
firstNameControl.setValue('firstName');
6870
firstNameControl.markAsTouched();
6971

7072
expect(component.isValid()).toEqual(true);
7173
});
7274

73-
it(`isValid returns false when there are controls with ValidationErrors and they are touched`, () => {
75+
it(`isValid returns false when there are controls with ValidationErrors and they are touched (default configuration)`, () => {
7476
component.for = [firstNameControl];
7577
firstNameControl.markAsTouched();
7678

7779
expect(component.isValid()).toEqual(false);
7880
});
7981

80-
it(`getErrorMessages returns the first error message per touched control`, () => {
82+
it(`getErrorMessages returns the first error message per touched control (default configuration)`, () => {
8183
component.for = [firstNameControl, middleNameControl, lastNameControl];
8284
firstNameControl.markAsTouched();
8385
// We skip middleNameControl on purpose, to ensure that it doesn't return it's error.
@@ -88,39 +90,72 @@ describe('ValidationMessagesComponent', () => {
8890
});
8991
});
9092

91-
describe('validation is shown when', () => {
92-
let submittedSubject: Subject<{}>;
93+
describe('when in', () => {
9394
let fixture: ComponentFixture<TestHostComponent>;
9495

95-
beforeEach(() => {
96-
submittedSubject = new Subject<{}>();
97-
TestBed.configureTestingModule({
98-
imports: [ReactiveFormsModule],
99-
declarations: [ValidationMessagesComponent, ValidationMessageComponent, TestHostComponent],
100-
providers: [{
101-
provide: FormDirective, useValue: {
102-
submitted: submittedSubject
103-
}
104-
}]
96+
describe('the default configuration validation is shown when', () => {
97+
let submittedSubject: Subject<{}>;
98+
99+
beforeEach(() => {
100+
submittedSubject = new Subject<{}>();
101+
TestBed.configureTestingModule({
102+
imports: [ReactiveFormsModule],
103+
declarations: [ValidationMessagesComponent, ValidationMessageComponent, TestHostComponent],
104+
providers: [{
105+
provide: FormDirective, useValue: {
106+
submitted: submittedSubject
107+
}
108+
}]
109+
});
110+
111+
fixture = TestBed.createComponent(TestHostComponent);
112+
fixture.detectChanges();
105113
});
106114

107-
fixture = TestBed.createComponent(TestHostComponent);
108-
fixture.detectChanges();
109-
});
115+
it(`the associated control is touched`, () => {
116+
fixture.componentInstance.firstNameControl.markAsTouched();
117+
fixture.componentInstance.lastNameControl.markAsTouched();
118+
fixture.detectChanges();
119+
120+
expectValidationIsShown();
121+
});
110122

111-
it(`the associated control is touched`, () => {
112-
fixture.componentInstance.firstNameControl.markAsTouched();
113-
fixture.componentInstance.lastNameControl.markAsTouched();
114-
fixture.detectChanges();
123+
it(`the form has been submitted`, () => {
124+
submittedSubject.next();
125+
fixture.detectChanges();
115126

116-
expectValidationIsShown();
127+
expectValidationIsShown();
128+
});
117129
});
118130

119-
it(`the form has been submitted`, () => {
120-
submittedSubject.next();
121-
fixture.detectChanges();
131+
describe('an alternative configuration', () => {
132+
const configuration: ReactiveValidationModuleConfiguration = {
133+
displayValidationMessageWhen: (control, formSubmitted) => true
134+
};
135+
136+
beforeEach(() => {
137+
spyOn(configuration, 'displayValidationMessageWhen').and.callThrough();
122138

123-
expectValidationIsShown();
139+
TestBed.configureTestingModule({
140+
imports: [ReactiveFormsModule, ReactiveValidationModule.forRoot(configuration)],
141+
declarations: [TestHostComponent],
142+
});
143+
144+
fixture = TestBed.createComponent(TestHostComponent);
145+
});
146+
147+
it('validation is shown when displayValidationMessageWhen returns true', () => {
148+
expect(configuration.displayValidationMessageWhen).not.toHaveBeenCalled();
149+
fixture.detectChanges();
150+
expect(configuration.displayValidationMessageWhen).toHaveBeenCalled();
151+
152+
expectValidationIsShown();
153+
});
154+
155+
it(`displayValidationMessageWhen's formSubmitted is undefined when a FormDirective is not provided`, () => {
156+
fixture.detectChanges();
157+
expect(configuration.displayValidationMessageWhen).toHaveBeenCalledWith(jasmine.any(FormControl), undefined);
158+
});
124159
});
125160

126161
function expectValidationIsShown() {

angular-reactive-validation/src/validation-messages/validation-messages.component.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, ContentChildren, QueryList, Input, ViewEncapsulation, AfterContentInit,
2-
OnDestroy, Optional, OnInit } from '@angular/core';
2+
OnDestroy, Optional, OnInit, Inject } from '@angular/core';
33
import { FormControl, ControlContainer, FormGroup } from '@angular/forms';
44
import { ValidationMessageComponent } from '../validation-message/validation-message.component';
55
import { ValidationError } from '../validation-error';
@@ -9,6 +9,8 @@ import { getControlPath } from '../get-control-path';
99
import { ObservableContainer } from '../observable-container';
1010
import { executeAfterContentInit } from '../execute-after-content-init';
1111
import { FormDirective } from '../form/form.directive';
12+
import { ReactiveValidationModuleConfiguration } from '../reactive-validation-module-configuration';
13+
import { ReactiveValidationModuleConfigurationToken } from '../reactive-validation-module-configuration-token';
1214

1315
@Component({
1416
selector: 'arv-validation-messages',
@@ -31,7 +33,8 @@ export class ValidationMessagesComponent implements OnInit, AfterContentInit, On
3133
private formSubmitted = false;
3234
private formSubmittedSubscription: Subscription;
3335

34-
constructor(@Optional() private controlContainer: ControlContainer, @Optional() private formSubmitDirective: FormDirective) { }
36+
constructor(@Optional() private controlContainer: ControlContainer, @Optional() private formSubmitDirective: FormDirective,
37+
@Optional() @Inject(ReactiveValidationModuleConfigurationToken) private configuration: ReactiveValidationModuleConfiguration) { }
3538

3639
@ContentChildren(ValidationMessageComponent) private messageComponents: QueryList<ValidationMessageComponent>;
3740

@@ -90,8 +93,10 @@ export class ValidationMessagesComponent implements OnInit, AfterContentInit, On
9093
}
9194

9295
private getFirstErrorPerControl() {
93-
return this._for.filter(control => control.touched || this.formSubmitted)
94-
.map(ValidationError.fromFirstError).filter(value => value !== undefined);
96+
return this._for.filter(control => this.configuration && this.configuration.displayValidationMessageWhen ?
97+
this.configuration.displayValidationMessageWhen(control, this.formSubmitDirective ? this.formSubmitted : undefined) :
98+
control.touched || this.formSubmitted
99+
).map(ValidationError.fromFirstError).filter(value => value !== undefined);
95100
}
96101

97102
/**

0 commit comments

Comments
 (0)