Skip to content

Commit

Permalink
feat: update forms doc
Browse files Browse the repository at this point in the history
  • Loading branch information
sdo-1A committed Apr 22, 2024
1 parent bbae039 commit 6769a51
Show file tree
Hide file tree
Showing 6 changed files with 558 additions and 848 deletions.
172 changes: 74 additions & 98 deletions docs/forms/FORM_ERRORS.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,19 @@
[Form errors](#introduction)

- [Form errors](#form-errors)
- [Form error store](#form-error-store)
- [Error object model](#error-object-model)
- [Creating error object](#creating-error-object)
- [Custom errors](#custom-errors)
- [Basic/primitive errors](#basicprimitive-errors)
- [Build error messages](#build-error-messages)
- [Display inline error messages](#display-inline-error-messages)
- [Basic errors](#basic-errors)
- [Custom errors](#custom-errors-1)
- [Add errors to the store](#add-errors-to-the-store)
- [Errors translation definition](#errors-translation-definition)
- [Custom errors](#custom-errors-2)
- [Primitive errors](#primitive-errors)

<a name="introduction"></a>

# Form errors

Handling the form errors in Otter context (container/presenter, localization ...), it's a bit different from creating a form in a component and do all the logic there.
<a name="store"></a>
Handling the form errors in the Otter context (container/presenter, localization, etc.) is a bit different from creating a form in a component and doing all the logic there.

### Form error store

To have the possibility to display inline error messages in the form and also in error panels (on the top of the page, above submit button ...) the best match is to have a dedicated store for the form errors. In this way we can listen to the store state and display the errors anywhere in the page.
The store is provided in __@o3r/forms__ package. See [Form Error Store](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/forms/src/stores/form-error-messages/form-error-messages.state.ts) for more details and state object model.
<a name="errormodel"></a>
To have the possibility of displaying inline error messages in the form and also in error panels (on the top of the page, above submit button, etc.),
the best option is to have a dedicated NgRX store for the form errors. This way we can listen to the store state and display the errors anywhere in the page.
The store is provided in the __@o3r/forms__ package. See [Form Error Store](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/forms/src/stores/form-error-messages/form-error-messages.state.ts)
for more details and to view the state object model.

#### Error object model

The store model object is __FormError__. See below the form errors object models.
The store model object extends the `FormError` interface (see its object model below).

- The __FormError__ contains an identifier for each component which has a form inside, plus the errors associated to that form.
- The `FormError` interface contains an identifier for each component that has a form inside, plus the errors associated to that form.

```typescript
/** Form's error messages identified by form id */
Expand All @@ -45,9 +26,8 @@ export interface FormError {
}
```

- __ElementError__
This object contains all the errors associated to the html element.
The identifier __htmlElementId__ can be used as an anchor link to focus on the html element on which the validation has failed
- The `ElementError` object contains all the errors associated to the HTML element.
The identifier `htmlElementId` can be used as an anchor link to focus on the HTML element where the validation failed.

```typescript
/** Error messages of the html element identified by its id */
Expand All @@ -60,15 +40,13 @@ export interface ElementError {
}
```

- __ErrorMessageObject__
- associated to an error message on a field.
- It will contain:
- __translationKey__ for the error message
- __longTranslationKey__ used for a more detailed message on the same error
- __translationParams__ translations parameters
- __validationError__ original error object
- `ErrorMessageObject` is associated to an error message on a field. It contains:
- `translationKey`: translation key for the short error message
- `longTranslationKey`: translation key for a more detailed message on the same error
- `translationParams`: translations parameters of the error message
- `validationError`: original error object

```typescript
`````typescript
/** The error object saved in the store for a specific element/formControl */
export interface ErrorMessageObject {
/**
Expand Down Expand Up @@ -100,38 +78,45 @@ export interface ErrorMessageObject {
* @example
* ```typescript
* {required: true}
* ```
* \```
* @example
* ```typescript
* {max: {max 12, actual: 31}}
* ```
*/
validationError?: {[key: string]: any};
}
```
`````

<a name="createerror"></a>
You can also find these interfaces [here](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/forms/src/core/errors.ts).

### Creating error object
### Creating an error object

The presenter has to implement the [Validator](https://angular.io/api/forms/NG_VALIDATORS) or [AsyncValidator](https://angular.io/api/forms/NG_ASYNC_VALIDATORS) in order to give us the possibility to define the error object which will be returned by the form.
The error message structure will be defined in the implementation of __validate__ method.
As __validate__ function should return a [ValidationErrors](https://angular.io/api/forms/ValidationErrors) object, which is a map of custom objects (with type _any_), we can prepare the returned object for the store of error messages. This will ease the process of adding the errors in the store.
We have to make sure that we are providing the __htmlElementId__ for the errors in the store which is matching the __html field__.
For this, the presenter is receiving an __id__ as input and for each field we are concatenating the __id__ with the __formControlName__. As the container is setting a __unique id__ we are sure that we have uniques html ids for the form fields.
The object returned by the __validate__ is the error object which is propagated to the container.
The presenter has to implement the [Validator](https://angular.io/api/forms/NG_VALIDATORS) or the [AsyncValidator](https://angular.io/api/forms/NG_ASYNC_VALIDATORS) interface
in order to give us the possibility of defining the error object that will be returned by the form.

There are 2 types of validators (see [Form Validation](./FORM_VALIDATION.md)), 2 categories of error messages:
The error message structure will be defined in the implementation of the `validate` function.

- one for __custom errors__ - set on the container
- one for __primitive errors__ - computed in the presenter.
As the `validate` function should return a [ValidationErrors](https://angular.io/api/forms/ValidationErrors) object, which is a map of custom objects (of type `any`),
we can prepare the returned object for the store of error messages. This will ease the process of adding the errors in the store.

<a name="customerrors"></a>
We have to make sure that we provide the `htmlElementId` of the errors in the store that match the __HTML fields__.
For this, the presenter receives an `id` as input and for each field we are concatenating, the `id` with the `formControlName`.
Since the container sets a __unique id__, we are sure to have unique HTML identifiers for the form fields.

#### Custom errors
The object returned by the `validate` function is the error object that is propagated to the container.

#### Categories of error messages

They are returned by __custom validators__ and have the type [CustomErrors](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/forms/src/core/custom-validation.ts) defined in __@o3r/forms__.
This one is using _customErrors_ key with an array of __ErrorMessageObject__ which has to contain all the custom errors for a form control or group.
There are two types of validators (see [Form Validation](./FORM_VALIDATION.md)) and therefore two categories of error messages:

- __Custom error__ - set in the container
- __Primitive error__ - computed in the presenter

###### Custom errors

They are returned by __custom validators__ and have the type `CustomErrors` (defined in [__@o3r/forms__](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/forms/src/core/custom-validation.ts)).
This type uses the `customErrors` key with an `ErrorMessageObject` array, which has to contain all the custom errors of a form control or a group.

```typescript
/**
Expand All @@ -143,25 +128,22 @@ export interface CustomErrors {
}
```

Error object model returned by the validator has to be compliant with the store model.
The error object model returned by the validator has to be compliant with the store model.

```typescript
// Example of returned object by the custom validator
{customErrors: [{translationKey, longTranslationKey, translationParams}]};
```

<a name="basicerrors"></a>

#### Basic/primitive errors

The error object structure has to be created in the presenter because the __basic validators__ are defined at presenter level (see [FORM_VALIDATION](./FORM_VALIDATION.md)).
###### Basic/primitive errors

<a name="builderror"></a>
The error object structure has to be created in the presenter because the __basic validators__ are defined at presenter level (see [Form Validation](./FORM_VALIDATION.md)).

#### Build error messages

We put in place a generic helper [__getFlatControlErrors__](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/forms/src/core/helpers.ts) in __@o3r/forms__.
This gets a flattened list of all the errors in the form and it's descendants, concatenating the __custom errors__; The object returned by the helper has [ControlFlatErrors](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/forms/src/core/flat-errors.ts) type.
We put in place a generic helper `getFlatControlErrors` in [__@o3r/forms__](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/forms/src/core/helpers.ts).
This function gets a flattened list of all the errors from the form and its descendants and concatenates the __custom errors__.
The object returned by the helper is of type `ControlFlatErrors` (defined in [__@o3r/forms__](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/forms/src/core/flat-errors.ts)).

```typescript
/**
Expand All @@ -181,7 +163,7 @@ export interface ControlFlatErrors {
}
```

Example of __validate__ method implementation
Below is an example of the implementation of a `validate` function:

```typescript
/// ----> in the presenter class
Expand Down Expand Up @@ -225,14 +207,14 @@ export class FormsPocPresComponent implements OnInit, Validator, FormsPocPresCon
return null;
}

const formErrors = getFlatControlErrors(this.travelerForm); // ---> use the helper to get the flat list of errors for the form
const formErrors = getFlatControlErrors(this.travelerForm); // ---> use the helper to get the flat list of errors from the form

const errors = formErrors.reduce((errorsMap: ValidationErrors, controlFlatErrors: ControlFlatErrors) => {
return {
// ...errorsMap,
[controlFlatErrors.controlName || 'global']: { // ---> use the 'global' key for the errors applied on the root form
htmlElementId: `${this.id}${controlFlatErrors.controlName || ''}`, // ---> The html id of the element
errorMessages: (controlFlatErrors.customErrors || []).concat( // ---> errors associated to the html element ( custom errors plus basic ones )
errorMessages: (controlFlatErrors.customErrors || []).concat( // ---> errors associated to the html element (custom errors plus basic ones)
controlFlatErrors.errors.map((error) => {
// Translation key creation
// As the primitive errors are linked to the presenter we use the component selector, the control name and the error key, to compute the translationKey
Expand Down Expand Up @@ -297,42 +279,38 @@ export class FormsPocPresComponent implements OnInit, Validator, FormsPocPresCon
}
```

This is only an example of implementation. The _translationKey_ and _translationParams_ can be different implemented depending on the use cases.
<a name="inlineerror"></a>
This is only an example of an implementation. The `translationKey` and `translationParams` can be implemented differently depending on the use cases.

### Display inline error messages

<a name="inlinebasicerror"></a>
Below, you will find examples of HTML implementations to display basic and custom errors as inline error messages.

#### Basic errors

```html
///----> presenter template
<!-- presenter template -->
<input type="date" formControlName="dateOfBirth" [id]="id + 'dateOfBirth'"></input>
<mat-error *ngIf="travelerForm.controls.dateOfBirth.errors?.max">
// use the translation object for the translationKey and get the translationParams from the error object returned by 'date-inline-input'.
<!-- use the translation object for the translationKey and get the translationParams from the error object returned by 'date-inline-input'. -->
{{translations.maxMonthInDate | o3rTranslate: {max: travelerForm.controls.dateOfBirth.errors?.max.max} }}
</mat-error>
```

<a name="inlinecustomerror"></a>

#### Custom errors

```html
///----> presenter template
<!-- presenter template -->
<input type="date" formControlName="dateOfBirth" [id]="id + 'dateOfBirth'"></input>
<mat-error *ngFor="let customError of travelerForm.controls.dateOfBirth.errors?.customErrors">
// translation key and params are already accessible in the error object returned by the custom validator
<!-- translation key and params are already accessible in the error object returned by the custom validator -->
{{customError.translationKey | o3rTranslate: customError.translationParams }}
</mat-error>
```

<a name="adderrortostore"></a>

### Add errors to the store

As we already defined the error message object, as the return of __validate__ method in the presenter, we can get the error messages and add them to the store, in the container. Check the example below.
As we have already defined the error message object as the return of the `validate` function in the presenter, we can get the error messages in the container and add them to the store.
Check the example below:

```typescript
/// ---> in the container
Expand Down Expand Up @@ -376,34 +354,33 @@ submitAction() {
}
```

In the example above we save the errors in the store when we execute the submit action. It can be done at valueChanges or statusChanges.
<a name="errorstranslation"></a>
In the example above, we save the errors in the store when we execute the submit action. This action can be called at `valueChanges` or `statusChanges` in the presenter.

### Errors translation definition

For the localization of the error messages we keep the same way we have today ([LOCALIZATION](../localization/LOCALIZATION.md)), but we have specific places where to define the default translations of error messages.
<a name="translationcustomerror"></a>
For the localization of the error messages, we keep the same way of working as we have today (check out [LOCALIZATION](../localization/LOCALIZATION.md)),
but we have specific places where to define the default translations of error messages.

#### Custom errors

Because the form validation depends on business logic and the custom validators are created in the container (see: [Form Validation](./FORM_VALIDATION.md)) we have to provide an error message for each validator and to ensure that the message is translatable.
We have to add the default translation keys, corresponding to the custom validators __in the container__ (_container.localization.json_ file).
Because the form validation depends on business logic and the custom validators are created in the container (see [Form Validation](./FORM_VALIDATION.md)),
we have to provide an error message for each validator and ensure that the message is translatable.
We have to add the default translation keys, corresponding to the custom validators, to the localization file __in the container__.

```typescript
// ---> in container class
/**
* Localization of the component
*/
@Input()
@Localization('./forms-poc-cont.localization.json') // Here we will define the error messages translation keys
public translations: FormsPocContTranslation;
// ---> in container class
/**
* Localization of the component
*/
@Input()
@Localization('./forms-poc-cont.localization.json') // Here we will define the translation keys of the error messages
public translations: FormsPocContTranslation;
```

Default values for the custom errors
Default values have to be defined for the custom errors, for example:

```json
// ----> forms-poc-cont.localization.json
...
"travelerForm.dateOfBirth.max": { // ---> travelerForm is the name we have chosen for the form
"description": "Validator for date of birth month",
"defaultValue": "Max value for the month should be {{ max }}"
Expand All @@ -415,26 +392,25 @@ Default values for the custom errors
...
```

<a name="translationbasicerror"></a>

#### Primitive errors

These validators are defined and applied at presenter level, so we have to define the translation of the error messages here.
Each possible validator should have a corresponding error message in __presenter.localization.json__ file.
Each possible validator should have a corresponding error message in the presenter's localization file.

```typescript
// ---> in presenter class
/**
* Localization of the component
*/
@Input()
@Localization('./forms-pres-cont.localization.json') // Here we will define the error messages translation keys
@Localization('./forms-pres-pres.localization.json') // Here we will define the translation keys of the error messages
public translations: FormsPocPresTranslation;
```

Default values for the custom errors
Default values have to be defined for the primitive errors, for example:

```json
// ----> forms-poc-pres.localization.json
// The first key is not related to forms
"o3r-forms-poc-pres.key.not.related.to.forms": {
"description": "Test Value with a translation",
Expand Down
Loading

0 comments on commit 6769a51

Please sign in to comment.