# Validation

## "Validators"

Les constructeurs des "controls" *(`FormControl`, `FormGroup` et `FormArray`)* acceptent en second paramètre **une liste de fonctions de validation** appelées "validators".

Les "validators" natifs d'Angular sont regroupés sous forme de méthodes statiques das la classe `Validators`.

{% tabs %}
{% tab title="book-form.component.ts" %}

```typescript
export class BookFormComponent {

    bookForm = new FormGroup({
        title: new FormControl(null, [
            Validators.required
        ]),
        description: new FormControl()
    });

    submitBook() {
        console.log(this.bookForm.value);
    }

}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Remarquez que l'ajout du "validator" ne change pas le comportement du composant : **la méthode `submitBook` continue à être appelée bien que la contrainte de validation ne soit pas respectée.**

C'est au composant de décider de l'action à mener en fonction de l'état des "controls".
{% endhint %}

Les "controls" disposent d'une série de **propriétés et de méthodes permettant d'en vérifier l'état** :

* `valid` : Valeur booléenne indiquant si le "control" est valide. Dans le cas d'un `FormGroup` ou `FormArray`, **le "control" est valide si les "controls" qui le composent sont tous valides**.&#x20;
* `errors` : "plain object" combinant les erreurs de tous les validateurs.  Vaut `null` si le "control" est valide.&#x20;
* `touched` : Valeur booléenne positionnée à `true` dès le déclenchement de l'événement `blur` *(i.e. l'utilisateur change de "focus")*.&#x20;
* `pristine` : Valeur booléenne indiquant si le "control" a été modifié.

### Exemple de désactivation du "submit"

{% tabs %}
{% tab title="book-form.component.html" %}

```markup
<button
    [disabled]="!bookForm.valid"
    type="submit">SUBMIT</button>
```

{% endtab %}
{% endtabs %}

## `hasError` & `getError`

Les méthodes `hasError` et `getError` sont deux méthodes "helpers" permettant d'**accéder plus facilement** aux informations d'erreur d'un "control".

{% tabs %}
{% tab title="book-form.component.html" %}

```markup
<div *ngIf="shouldShowTitleRequiredError()">Title is required.</div>
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="book-form.component.ts" %}

```typescript
shouldShowTitleRequiredError() {

    const title = this.bookForm.controls.title;

    return title.touched && title.hasError('required');

}
```

{% endtab %}
{% endtabs %}

{% hint style="danger" %}
Préférez les méthodes `hasError` et `getError` aux opérateurs ternaires *(`title.errors ? title.errors.required : null`)* !
{% endhint %}

## "Validator" personnalisé

Un "validator" est une fonction qui est **appelée à chaque changement de la valeur du "control"** afin d'en vérifier la validité. Si la valeur est valide, le "control" retourne **`null`** ou un **objet d'erreur** dans le cas contraire.

{% tabs %}
{% tab title="valid-book-title.validator.ts" %}

```typescript
import { ValidatorFn } from '@angular/forms';
​
export const validBookTitle: ValidatorFn = (control) => {

    /* Is not valid. */
    if (/learn .*? in one day/i.test(control.value)) {
        return {
            'validBookTitle': {
                reason: 'blacklisted',
                value: control.value
            }
        };
    }

    /* Is valid. */
    return null;

};
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="book-form.component.ts" %}

```typescript
bookForm = new FormGroup({
    title: new FormControl(null, [
        Validators.required,
        validBookTitle
    ]),
    description: new FormControl()
});
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Les informations d'erreur (`reason` et `value`) sont alors accessibles grâce à la méthode `getError`.

```typescript
const error = bookForm.getError('validBookTitle', 'title');
if (error != null) {
    console.log(error.reason);
    console.log(error.value);
}
```

{% endhint %}

### "Validator" paramétré

Tels que le "validator" `minLength`, **certains "validators" ont besoin de paramètres** pour personnaliser leur comportement.\
Dans ce cas, il suffit d'implémenter une **"factory" de "validators"** *(i.e. : une fonction qui retourne des fonctions de type "validator")*.

{% tabs %}
{% tab title="valid-pattern.validator.ts" %}

```typescript
export type ValidPattern = (args: {blacklistedPatternList: RegExp[]}) => ValidatorFn;

const validPattern: ValidPattern = ({blacklistedPatternList}) => {

    return (control) => {

        for (const blacklistedPattern of blacklistedPatternList) { ... }

        return null;

    };

};
```

{% endtab %}
{% endtabs %}

Ou en utilisant **le "currying" des** [**Arrow Functions**](/ecmascript-6+/arrow-functions.md) :

{% tabs %}
{% tab title="valid-pattern.validator.ts" %}

```typescript
export type ValidPattern = (args: {blacklistedPatternList: RegExp[]}) => ValidatorFn;

const validPattern: ValidPattern = ({blacklistedPatternList}) => control => {

    for (const blacklistedPattern of blacklistedPatternList) {

        const result = blacklistedPattern.exec(control.value);

        if (result != null) {
            return {
                'validPattern': {
                    reason: 'blacklisted',
                    match: result[0],
                    value: control.value
                }
            };
        }

    }

    return null;

};
```

{% endtab %}
{% endtabs %}

Le "validator" est alors personnalisé à l'utilisation :

{% tabs %}
{% tab title="book-form.component.ts" %}

```typescript
bookForm = new FormGroup({
    title: new FormControl(null, [
        Validators.required,
        validPattern({
            blacklistedPatternList: [
                /^learn .*? in one day/i,
                /hero/i,
                /ninja/i
            ]
        })
    ]),
    description: new FormControl()
});
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Même l'erreur contient alors des informations personnalisées :

```typescript
const error = bookForm.getError('validPattern', 'title');
if (error != null) {
    console.log(error.reason); // blacklisted
    console.log(error.value); // Become an Angular Ninja
    console.log(error.match); // Ninja
}
```

{% endhint %}

## "Async Validators"'

Il est parfois **nécessaire d'implémenter des "validators" asynchrones** *(e.g. vérification distante via une API)*.\
Un "validator" asynchrone se comporte de la même façon qu'un "validator" synchrone mais au lieu de retourner `null` ou un objet d'erreur, **il doit retourner un `Observable`**.

{% hint style="success" %}
Les **"validators" asynchrones peuvent être transmis au "control"** par paramètre ordonné *(3ème paramètre après la valeur initiale et les validators synchrones)* mais **il est préférable d'utiliser un objet** plus explicite en guise de second paramètre :

```typescript
new FormControl(null, {
    validators: [Validators.required],
    asyncValidators: [myAsyncValidator]
})
```

{% endhint %}

### Validators & "Dependency Injection"

Les "validators" *(et plus particulièrement les "validators" asynchrones)* ont parfois **besoin d'accéder aux services** *(au sens Angular)* mais sans [Dependency Injection](/angular/dependency-injection.md) pour les "validators", l'astuce consiste à **implémenter la "factory" du "validator" dans un service dédié afin de profiter de la "Dependency Injection"**.

{% tabs %}
{% tab title="valid-pattern-factory.ts" %}

```typescript
@Injectable({
    providedIn: 'root'
})
export class ValidPatternFactory {

    constructor(private _httpClient: HttpClient) {
    }

    create(): AsyncValidatorFn {
        return control => {

            return this._httpClient.post<ApiValidatorResult>('/validations', {
                validatorId: 'VALIDATOR_ID',
                keywords: control.value
            })
                .pipe(map(result => result.errors));

        };
    }

}
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="book-form.component.ts" %}

```typescript
export class BookFormComponent {

    bookForm: FormGroup;
​
    constructor(private _validPatternFactory: ValidPatternFactory) {
        this.bookForm = new FormGroup({
            title: new FormControl(null, {
                validators: [Validators.required],
                asyncValidators: [this._validPatternFactory.create()]
            }),
            description: new FormControl()
        });
    }

}
```

{% endtab %}
{% endtabs %}

### Annulation des Observables

{% hint style="warning" %}
Dès la saisie d'une nouvelle valeur, l'`Observable` **récupéré précédemment sera annulé** avant de déclencher le traitement du nouvel `Observable` retourné *(via un `subscribe`)*.
{% endhint %}

## Personnalisation de l'affichage

Afin de **faciliter la personnalisation du CSS en fonction de la validité** des éléments du formulaire, Angular ajoute automatiquement les classes CSS suivantes en fonction de l'état du "control" :

* `.ng-valid`
* `.ng-invalid`
* `.ng-pending`
* `.ng-pristine`
* `.ng-dirty`
* `.ng-untouched`
* `.ng-touched`

```css
input.ng-touched.ng-invalid {
    border-width: 1px;
    border-left: red solid 5px;
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://guide-angular.wishtack.io/angular/formulaires/reactive-forms/validation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
