Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.

Commit 55fd44a

Browse files
feat: integrate with MatInput; support arbitrary showWhen
1 parent 27e6bbf commit 55fd44a

21 files changed

+555
-93
lines changed

README.md

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ The design of this library promotes less boilerplate code, which keeps your temp
2828

2929
## Table of Contents
3030

31-
- [How it works](#how_it_works)
31+
- [How it works](#how-it-works)
3232
- [Installation](#installation)
3333
- [Usage](#usage)
3434
- [Advanced configuration](#configuration)
35-
- [Handling form submission](#handling_form_submission)
36-
- [Getting error details](#getting_error_details)
35+
- [Handling form submission](#handling-form-submission)
36+
- [Getting error details](#getting-error-details)
3737
- [Styling](#styling)
3838
- [Miscellaneous](#miscellaneous)
3939
- [Development](#development)
@@ -51,8 +51,8 @@ For more info about this see [Advanced configuration](#configuration).
5151

5252
## Installation
5353

54-
* For Angular >= v13 use @ngspot/ngx-errors@3.x
55-
* For Angular < v13 use @ngspot/ngx-errors@2.x
54+
- For Angular >= v13 use @ngspot/ngx-errors@3.x
55+
- For Angular < v13 use @ngspot/ngx-errors@2.x
5656

5757
### NPM
5858

@@ -121,6 +121,24 @@ export class MyComponent implements OnInit {
121121
}
122122
```
123123

124+
### Use case with a template driven form control:
125+
126+
```ts
127+
@Component({
128+
selector: 'my-component',
129+
template: `
130+
<input [(ngModel)]="email" #emailModel="ngModel" required type="email" />
131+
132+
<div [ngxErrors]="emailModel.control">
133+
<div ngxError="required">Email is required</div>
134+
</div>
135+
`,
136+
})
137+
export class MyComponent implements OnInit {
138+
email: string;
139+
}
140+
```
141+
124142
## Configuration
125143

126144
Configure when to show messages for whole module by using `.configure()` method:
@@ -185,9 +203,7 @@ You can override the configuration specified at the module level by using `[show
185203
This will be shown when control is dirty
186204
</div>
187205

188-
<div ngxError="min">
189-
This will be shown when control is touched and dirty
190-
</div>
206+
<div ngxError="min">This will be shown when control is touched and dirty</div>
191207
</div>
192208
```
193209

@@ -245,53 +261,64 @@ Include something similar to the following in global CSS file:
245261
ngx-errors library provides a couple of misc function that ease your work with forms.
246262

247263
### **dependentValidator**
264+
248265
Makes it easy to trigger validation on the control, that depends on a value of a different control
249266

250267
Example with using `FormBuilder`:
268+
251269
```ts
252270
import { dependentValidator } from '@ngspot/ngx-errors';
253271

254272
export class LazyComponent {
255273
constructor(fb: FormBuilder) {
256274
this.form = fb.group({
257275
password: ['', Validators.required],
258-
confirmPassword: ['', dependentValidator<string>({
259-
watchControl: f => f!.get('password')!,
260-
validator: (passwordValue) => isEqualToValidator(passwordValue)
261-
})],
276+
confirmPassword: [
277+
'',
278+
dependentValidator<string>({
279+
watchControl: (f) => f!.get('password')!,
280+
validator: (passwordValue) => isEqualToValidator(passwordValue),
281+
}),
282+
],
262283
});
263284
}
264285
}
265286

266287
function isEqualToValidator<T>(compareVal: T): ValidatorFn {
267-
return function(control: AbstractControl): ValidationErrors | null {
288+
return function (control: AbstractControl): ValidationErrors | null {
268289
return control.value === compareVal
269290
? null
270291
: { match: { expected: compareVal, actual: control.value } };
271-
}
292+
};
272293
}
273294
```
274295

275296
The `dependentValidator` may also take `condition`. If provided, it needs to return true for the validator to be used.
276297

277298
```ts
278299
const controlA = new FormControl('');
279-
const controlB = new FormControl('', dependentValidator<string>({
280-
watchControl: () => controlA,
281-
validator: () => Validators.required,
282-
condition: (val) => val === 'fire'
283-
}));
300+
const controlB = new FormControl(
301+
'',
302+
dependentValidator<string>({
303+
watchControl: () => controlA,
304+
validator: () => Validators.required,
305+
condition: (val) => val === 'fire',
306+
})
307+
);
284308
```
309+
285310
In the example above, the `controlB` will only be required when `controlA` value is `'fire'`
286311

287312
### **extractTouchedChanges**
313+
288314
As of today, the FormControl does not provide a way to subscribe to the changes of `touched` status. This function lets you do just that:
289315

290316
```ts
291317
* const touchedChanged$ = extractTouchedChanges(formControl);
292318
```
293319

294320
### **markDescendantsAsDirty**
321+
295322
As of today, the FormControl does not provide a way to mark the control and all its children as `dirty`. This function lets you do just that:
296323

297324
```ts

angular.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@
5454
"projects/playground/src/favicon.ico",
5555
"projects/playground/src/assets"
5656
],
57-
"styles": ["projects/playground/src/styles.scss"],
57+
"styles": [
58+
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
59+
"projects/playground/src/styles.scss"
60+
],
5861
"scripts": [],
5962
"vendorChunk": true,
6063
"extractLicenses": false,
@@ -122,7 +125,10 @@
122125
"projects/playground/src/favicon.ico",
123126
"projects/playground/src/assets"
124127
],
125-
"styles": ["projects/playground/src/styles.scss"],
128+
"styles": [
129+
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
130+
"projects/playground/src/styles.scss"
131+
],
126132
"scripts": []
127133
}
128134
}

package-lock.json

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
"private": true,
2626
"dependencies": {
2727
"@angular/animations": "~13.1.0",
28+
"@angular/cdk": "^13.1.1",
2829
"@angular/common": "~13.1.0",
2930
"@angular/compiler": "~13.1.0",
3031
"@angular/core": "~13.1.0",
3132
"@angular/forms": "~13.1.0",
33+
"@angular/material": "^13.1.1",
3234
"@angular/platform-browser": "~13.1.0",
3335
"@angular/platform-browser-dynamic": "~13.1.0",
3436
"@angular/router": "~13.1.0",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { InjectionToken } from '@angular/core';
2+
import { ErrorStateMatcher } from '@angular/material/core';
3+
4+
export type CustomErrorStateMatchers = { [key: string]: ErrorStateMatcher };
5+
6+
/**
7+
* Provides a way to add to available options for when to display an error for
8+
* an invalid control. Options that come by default are
9+
* `'touched'`, `'dirty'`, `'touchedAndDirty'`, `'formIsSubmitted'`.
10+
*/
11+
export const CUSTOM_ERROR_STATE_MATCHERS =
12+
new InjectionToken<CustomErrorStateMatchers>('CUSTOM_ERROR_STATE_MATCHERS');
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Inject, Injectable, Optional } from '@angular/core';
2+
import { AbstractControl, FormGroupDirective, NgForm } from '@angular/forms';
3+
import {
4+
ErrorStateMatcher,
5+
ShowOnDirtyErrorStateMatcher,
6+
} from '@angular/material/core';
7+
import {
8+
CustomErrorStateMatchers,
9+
CUSTOM_ERROR_STATE_MATCHERS,
10+
} from './custom-error-state-matchers';
11+
12+
@Injectable({ providedIn: 'root' })
13+
export class ErrorStateMatchers {
14+
private matchers: { [key: string]: ErrorStateMatcher } = {};
15+
16+
constructor(
17+
showOnTouchedErrorStateMatcher: ShowOnTouchedErrorStateMatcher,
18+
showOnDirtyErrorStateMatcher: ShowOnDirtyErrorStateMatcher,
19+
showOnTouchedAndDirtyErrorStateMatcher: ShowOnTouchedAndDirtyErrorStateMatcher,
20+
showOnSubmittedErrorStateMatcher: ShowOnSubmittedErrorStateMatcher,
21+
@Optional()
22+
@Inject(CUSTOM_ERROR_STATE_MATCHERS)
23+
customErrorStateMatchers: CustomErrorStateMatchers
24+
) {
25+
this.matchers['touched'] = showOnTouchedErrorStateMatcher;
26+
this.matchers['dirty'] = showOnDirtyErrorStateMatcher;
27+
this.matchers['touchedAndDirty'] = showOnTouchedAndDirtyErrorStateMatcher;
28+
this.matchers['formIsSubmitted'] = showOnSubmittedErrorStateMatcher;
29+
if (customErrorStateMatchers) {
30+
this.matchers = { ...this.matchers, ...customErrorStateMatchers };
31+
}
32+
}
33+
34+
get(showWhen: string): ErrorStateMatcher | undefined {
35+
return this.matchers[showWhen];
36+
}
37+
38+
validKeys(): string[] {
39+
return Object.keys(this.matchers);
40+
}
41+
}
42+
43+
@Injectable()
44+
export class ShowOnTouchedErrorStateMatcher {
45+
isErrorState(
46+
control: AbstractControl | null,
47+
form: FormGroupDirective | NgForm | null
48+
): boolean {
49+
return !!(
50+
control &&
51+
control.invalid &&
52+
(control.touched || (form && form.submitted))
53+
);
54+
}
55+
}
56+
57+
@Injectable()
58+
export class ShowOnTouchedAndDirtyErrorStateMatcher
59+
implements ErrorStateMatcher
60+
{
61+
isErrorState(
62+
control: AbstractControl | null,
63+
form: FormGroupDirective | NgForm | null
64+
): boolean {
65+
return !!(
66+
control &&
67+
control.invalid &&
68+
((control.dirty && control.touched) || (form && form.submitted))
69+
);
70+
}
71+
}
72+
73+
@Injectable()
74+
export class ShowOnSubmittedErrorStateMatcher implements ErrorStateMatcher {
75+
isErrorState(
76+
control: AbstractControl | null,
77+
form: FormGroupDirective | NgForm | null
78+
): boolean {
79+
return !!(control && control.invalid && form && form.submitted);
80+
}
81+
}

0 commit comments

Comments
 (0)