forked from ngneat/error-tailor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.md
387 lines (307 loc) · 13.5 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
<p align="center">
<img width="20%" height="20%" src="./logo.svg">
</p>
<br />
> Making sure your tailor-made error solution is seamless!
[![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?style=flat-square)]()
[![commitizen](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)]()
[![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)]()
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
[![All Contributors](https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square)](#contributors-)
[![ngneat](https://img.shields.io/badge/@-ngneat-383636?style=flat-square&labelColor=8f68d4)](https://github.com/ngneat/)
[![spectator](https://img.shields.io/badge/tested%20with-spectator-2196F3.svg?style=flat-square)]()
The Error Tailor offers seamless handling of form errors, saving you the trouble of repeating the error boilerplate.
It's fully customizable, so you can control when, where, and how each form field's errors are displayed.
Sit back, relax, and let the Error Tailor do all the work!
<img src="./demo.gif">
<a href="https://www.buymeacoffee.com/basalnetanel" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
## Getting Started
Run `npm install @ngneat/error-tailor` and add the imports to your application:
<!-- prettier-ignore-start -->
```ts
import { provideErrorTailorConfig } from '@ngneat/error-tailor';
bootstrapApplication(AppComponent, {
providers: [
provideErrorTailorConfig({
errors: {
useValue: {
required: 'This field is required',
minlength: ({ requiredLength, actualLength }) =>
`Expect ${requiredLength} but got ${actualLength}`,
invalidAddress: error => `Address isn't valid`
}
}
})
]
})
```
<!-- prettier-ignore-end -->
The `errors` config property takes a partial `Provider`, that should provide a `HashMap<string | (err:any) => string>` that is an object with keys corresponding to the errors name that you want to handle, and values that can be a simple string, or function that return a string used as error message to be shown.
Now the only thing you need to add is the `errorTailor` directive to your form:
```html
<form [formGroup]="form" errorTailor>
<div class="form-group">
<input class="form-control" formControlName="name" placeholder="Name" />
</div>
<section formGroupName="address">
<div class="form-group">
<input class="form-control" formControlName="city" placeholder="City" />
</div>
<div class="form-group">
<input class="form-control" formControlName="country" placeholder="Country" />
</div>
</section>
<div class="form-group">
<select formControlName="animal" class="form-control">
<option *ngFor="let option of options; index as index" [ngValue]="option">
{{ option.label }}
</option>
</select>
</div>
<button class="btn btn-success">Submit</button>
</form>
```
```ts
import { errorTailorImports } from '@ngneat/error-tailor';
@Component({
selector: 'app-root',
standalone: true,
imports: [errorTailorImports, ReactiveFormsModule]
})
export class AppComponent {
private builder = inject(FormBuilder);
form: FormGroup;
ngOnInit() {
this.form = this.builder.group({
name: ['', [Validators.required, Validators.minLength(3)]],
terms: [false, Validators.requiredTrue],
animal: [null, Validators.required],
address: this.builder.group(
{
city: ['', Validators.required],
country: ['']
},
{ validator: addressValidator }
)
});
}
}
```
The directive will show all errors for a form field automatically in two instances - on the field element blur and on form submit.
## Inputs
- `controlErrorsClass` - A custom classes that'll be added to the control error component and override custom classes from global config, a component that is added after the form field when an error needs to be displayed:
```html
<input class="form-control" formControlName="city"
placeholder="City" controlErrorsClass="my-class other-class" />
```
- `controlCustomClass` - A custom classes that'll be added to the control if control has error.
```html
<input class="form-control" formControlName="city"
placeholder="City" controlCustomClass="my-custom-class other-custom-class" />
```
- `controlErrorsTpl` - A custom error template to be used instead of the control error component's default view:
```html
<form errorTailor>
<ng-template let-error let-text="text" #tpl> {{ error | json }} {{ text }} </ng-template>
<div class="form-group">
<input class="form-control" ngModel required name="name" [controlErrorsTpl]="tpl" />
</div>
<button class="btn btn-success">Submit</button>
</form>
```
- `controlErrorAnchor` - A custom `anchor` element for the control error component. The default anchor is the form field element:
```html
<div class="form-check form-group">
<input type="checkbox" formControlName="terms" id="check" [controlErrorAnchor]="anchor" />
<label class="form-check-label" for="check">
I agree to the terms and conditions
</label>
<ng-template controlErrorAnchor #anchor="controlErrorAnchor"></ng-template>
</div>
```
The custom `anchor` can also be added as a directive, in which case it'll act as the anchor for any nested form fields:
```html
<div class="form-check form-group" controlErrorAnchor>
<input type="checkbox" formControlName="terms" id="check" />
<label class="form-check-label" for="check">
I agree to the terms and conditions
</label>
</div>
```
- `controlErrors` - Additional errors to use for the form field, that aren't specified in the config:
```html
<input class="form-control" formControlName="country" placeholder="Country"
[controlErrors]="extraErrors" />
```
- `controlErrorsIgnore` - A custom attribute on a form field element to skip instantiating of a control error component on it.
One typical case when to use it is radio buttons in the same radio group where it's enough to show only one error message and not all of them for each separate radio button.
```html
<div class="form-group">
Communication language:
<input type="radio" name="languages" formControlName="languages"
value="en" id="en" [controlErrorAnchor]="anchorRadio" />
<label class="form-radio-label" for="en">English</label>
<input type="radio" name="languages" formControlName="languages"
value="de" id="de" controlErrorsIgnore />
<label class="form-radio-label" for="de">German</label>
<input type="radio" name="languages" formControlName="languages"
value="cs" id="cs" controlErrorsIgnore />
<label class="form-radio-label" for="cs">Czech</label>
<ng-template controlErrorAnchor #anchorRadio="controlErrorAnchor"></ng-template>
</div>
```
- `controlErrorsOnAsync` - To modify the error display behavior to not show errors from async validators, set the following input:
```html
<input [controlErrorsOnAsync]="false" formControlName="name" />
```
- `controlErrorsOnBlur` - To modify the error display behavior to not show errors on blur, set the following input:
```html
<input [controlErrorsOnBlur]="false" formControlName="name" />
```
- To modify the error display behavior and show the errors on submission alone, we can disable both `controlErrorsOnBlur` and `controlErrorsOnAsync`:
```html
<input [controlErrorsOnBlur]="false" [controlErrorsOnAsync]="false" formControlName="name" />
```
- `controlErrorsOnChange` - To modify the error display behavior to show/hide the errors on every change, set the following input:
```html
<input [controlErrorsOnChange]="true" formControlName="name" />
```
## Methods
- `showError()` - Programmatic access to show a control error component (without a blur or a submit event). A validation error should still exist on that element. The key is the published `exportAs` reference of `errorTailor` to a directive instance of `ControlErrorsDirective` and calling its public method `showError()`.
Syntax as `@ViewChild('gdprErrorTailor', { static: true }) gdprErrorTailor: ControlErrorsDirective;` is used to get the reference and later call `this.gdprErrorTailor.showError()`.
- `hideError()` - Programmatic access to hide an already shown control error component with the same logic as `showError()`, so for example: `this.gdprErrorTailor.hideError()`.
```html
<input type="checkbox" formControlName="gdpr" #gdprErrorTailor="errorTailor" />
```
## CSS Styling
The library adds a `form-submitted` to the submitted form. You can use it to style your inputs:
```css
.form-submitted input.ng-invalid,
.form-submitted select.ng-invalid {
border-color: #dc3545;
}
```
## Config
- `blurPredicate` - Elements that should listen the `focusout` event. The default predicate is:
```ts
{
blurPredicate(element) {
return element.tagName === 'INPUT' || element.tagName === 'SELECT';
}
}
```
- `controlClassOnly` - Optional. If `true`, the error component won't be created and only the error class will be added to the control. Default is `false`.
- `controlErrorsClass` - Optional. A custom classes that'll be added to the control error component. Can be override if you set attribute `controlErrorsClass` on control
- `controlCustomClass` - Optional. A custom classes that'll be added to the control if control has error. Can be override if you set attribute `controlCustomClass` on control
- `controlErrorComponent` - Optional. Allows changing the default component that is used to render
the errors. This component should implement the `ControlErrorComponent` interface. If you only need to
replace the error component's template, you may derive it from the default component,
`DefaultControlErrorComponent`, and provide the requisite HTML template.
A common example is when using Ionic forms where each form field is wrapped in an `ion-item` and errors
are best displayed as a sibling `ion-item` of the field. Example below shows how this can be done using
a custom control error component.
For example:
```ts
// Custom error component that will replace the standard DefaultControlErrorComponent.
@Component({
standalone: true,
imports: [errorTailorImports],
template: `
<ion-item lines="none" class="ion-text-wrap" [class.hide-control]="hideError">
<ion-label color="danger" class="ion-no-margin ion-text-wrap" stacked>
{{ errorText }}
</ion-label>
</ion-item>
`
})
export class IonicControlErrorComponent extends DefaultControlErrorComponent {
}
bootstrapApplication(AppComponent, {
providers: [
provideErrorTailorConfig({
errors: {
useValue: {
required: 'This field is required'
}
},
controlErrorComponent: IonicControlErrorComponent
})
]
})
```
- `controlErrorComponentAnchorFn` - Optional. A hook function that allows the error component's
HTML element to be repositioned in the DOM. By default error components are inserted at the
bottom of the field with error. If your UI layout dictates a different positioning
scheme, you may use this hook.
Since this error element can be placed anywhere in the DOM, it also has to be
removed when the error component is destroyed. To provide for this, this
function should return a callback that will then be invoked when the error component
is destroyed. You may use this to remove the error HTML element that you inserted
into the DOM yourself.
Example below shows how the Ionic specific error component is repositioned in the DOM
to suit Ionic's form layout. `hostElem` is the HTML element for the form control and
`errorElem` is the HTML element for the error component.
```ts
anchorIonicErrorComponent(hostElement: Element, errorElement: Element) {
hostElement.parentElement.insertAdjacentElement('afterend', errorElement);
return () => {
let errorNode = hostElement.parentElement.querySelector('custom-control-error');
if (errorNode) {
errorNode.remove();
}
};
}
bootstrapApplication(AppComponent, {
providers: [
provideErrorTailorConfig({
errors: {
useValue: {
required: 'This field is required'
}
},
controlErrorComponent: IonicControlErrorComponent,
controlErrorComponentAnchorFn: anchorIonicErrorComponent
})
]
})
```
- `controlErrorsOn` - Optional. An object that allows the default behavior for showing the errors to be overridden. (each individual property in the object is optional, so it's possible to override only 1 setting)
```ts
{
controlErrorsOn: {
async: true, // (default: true)
blur: true, // (default: true)
change: true, // (default: false)
}
}
```
## Recipes
### I18n Example
Here's how to support i18n:
```ts
import { TranslocoService } from '@ngneat/transloco';
bootstrapApplication(AppComponent, {
providers: [
provideErrorTailorConfig({
errors: {
useFactory(service: TranslocoService) {
return {
required: error => service.translate('errors.required')
};
},
deps: [TranslocoService]
}
})
]
})
```
### Control Error Style
Here's a default style you can use for the error component:
```css
.control-error {
width: 100%;
margin-top: 0.25rem;
font-size: 12px;
color: #dc3545;
}
```