Angular: Les formes réactives ne sont pas fortement typées

Créé le 30 déc. 2016  ·  90Commentaires  ·  Source: angular/angular

[x] feature request
  • Version angulaire : 2

Les formulaires réactifs sont destinés à être utilisés dans des formulaires complexes, mais les valueChanges du contrôle sont Observable<any> , ce qui est totalement contraire aux bonnes pratiques pour le code complexe.

Il devrait y avoir un moyen de créer des contrôles de formulaire fortement typés.

forms feature high

Commentaire le plus utile

Hé, je voulais partager une mise à jour de l'équipe Angular : nous vous entendons que c'est un gros problème. Nous commencerons bientôt à travailler sur des formulaires plus fortement dactylographiés, ce qui inclura l'examen des PR existants et l'examen à nouveau de tous vos commentaires. Merci à tous d'avoir pris le temps de laisser vos pensées!

Tous les 90 commentaires

lié #11279

Ceci n'est pas lié au #11279.

S'il vous plaît expliquer comment ce n'est pas lié?
Ce que vous voulez, c'est que le contrôle abstrait soit générique, n'est-ce pas ? C'est la seule façon pour valueChanges d'avoir un type qui n'est pas Observable<any> il n'y aurait pas d'autre moyen de déduire le type.
C'est exactement ce que demande le #5404, ce qui signifie que cela est lié au #11279
S'il existe une autre façon de mettre en œuvre cela sans faire de AbstractControl un générique, veuillez l'expliquer.

Utiliser get<Type> comme dans #11279 est définitivement une mauvaise solution. Si TypeScript avait quelque chose comme Java Unbounded Wildcard, get l'utiliserait, et non any . Peut-être que quelque chose peut être fait de la même manière avec une interface vide ?

Il y a aussi https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html keyof . Les fonctionnalités de TypeScript 2.1 peuvent être très intéressantes à étudier pour implémenter des contrôles de formulaire fortement typés.

Dans l'état actuel des choses, malheureusement, je ne pense pas que ce soit utilisable pour une grande application et j'ai besoin de concevoir quelque chose par-dessus.

Je viens de remarquer que dans TS 2.2 (https://github.com/Microsoft/TypeScript/wiki/Roadmap#22-february-2017) ils ont prévu des types génériques par défaut (https://github.com/Microsoft/TypeScript/ issues/2175) une fois que nous avons cela, je pense que ce pourrait être une bonne idée de revoir ce problème car nous pourrions faire AbstractControl générique comme AbstractControl<T = any> où le T est le type de la valeur renvoyée par valueChanges qui serait un Observable<T> . Ce ne serait pas une bonne idée de le faire actuellement car ce serait un changement de rupture massif, mais avec les génériques par défaut, à moins que je ne les comprenne mal, ce ne sera pas un changement de rupture.

Petite mise à jour à ce sujet, il semble que les génériques par défaut aient été déplacés vers TS2.3 . Donc, avec la prise en charge de TS 2.1 par Angular avec la version 4.0, cela ne devrait pas tarder avant qu'ils soient en mesure de prendre en charge TS 2.3, c'est maintenant que nous devons attendre pour revoir cela.

TypeScript 2.3 avec les types génériques par défaut est déjà là, avons-nous des plans lorsque la prise en charge de TS 2.3 en angulaire sera prête ?

@desfero attend le #16707 pour que la version soit mise à niveau vers TS2.3

+1 aimerait voir cette fonctionnalité. Quelqu'un y travaille ?

Cela pourrait être utile - Angular Typesafe Reactive Forms

Petite mise à jour à ce sujet :
Selon mon commentaire ici : https://github.com/angular/angular/pull/16828#issuecomment -337034655
la mise en œuvre de génériques dans l'API Forms actuelle n'est pas possible sans modifications importantes.
Donc, soit des changements de rupture sont nécessaires
Ou une réécriture complète des formulaires

Il s'avère donc que mon commentaire précédent était incorrect.
J'ai pu implémenter cela sur l'API Forms actuelle comme vous pouvez le voir ici # 20040

@Toxicable qui a toujours le problème de ne pas pouvoir refactoriser en toute sécurité. get('person') par exemple, n'utilise pas vraiment le symbole lui-même. L'exemple ci-dessus, de @rpbeukes , a une manière modernisée d'utiliser essentiellement le symbole de l'objet, par exemple. get(obj.person) sans utiliser la chaîne. Cela serait préférable au fait d'avoir simplement des types de retour.

@howiempt

get('person') par exemple, n'utilise pas vraiment le symbole lui-même

Je n'ai aucune idée de ce que vous entendez par là, de quel symbole parlez-vous ici ?
Dans mon implémentation, vous pouvez faire quelque chose comme

let g = new FormGroup({
  'name': new FormControl('Toxicable'),
  'age': new FormControl(22),
})

g.get('name') //AbstractControl<string>
g.get('age') //AbstractControl<number>

get(obj.person) sans utiliser la chaîne

Cela ne permet pas de traverser plusieurs FormGroups.
Bien que ma méthode ne puisse pas déduire les types dans ce scénario, l'idée de mon PR est d'ajouter des types génériques sans casser les modifications ou introduire de nouvelles API (à part les génériques)

@Toxicable Je comprends que votre changement est destiné à ne pas casser les choses, sans essayer de critiquer votre solution. L'autre implémentation (rétrofit) permet d'utiliser une propriété réelle plutôt qu'une chaîne. En référençant le champ par chaîne, si ce nom de propriété change, crée une rupture, ce qui pour moi n'est pas très sûr. Par exemple, changer le nom du champ de 'name' à 'firstName' serait cassé si je ne modifiais pas toutes les références g.get('name'). Si je pouvais faire quelque chose comme

class PersonDetails {
  name: string;
  age: number;
}
let g = new FormGroup<PersonDetails>({
  name: new FormControl('Toxicable'),
  age: new FormControl(22),
})

g.get(name) //AbstractControl<string>
g.get(age) //AbstractControl<number>

Ils seraient tous des références serrées. La solution de modernisation le fait d'une manière légèrement piratée, mais résout également ce problème.

@Toxicable merci pour le PR. J'ai hâte de l'utiliser :)

Je suis d'accord avec @howiempt , si nous pouvons obtenir quelque chose comme ça, ce serait le premier prix :

g.get(x => x.name) //AbstractControl

Encore une fois, je ne sais pas vraiment dans quelle mesure cela est faisable dans le cadre plus large des choses.
Je fais confiance à votre jugement.

Continuez votre bon travail et merci pour la réponse rapide.

Je pense que cette méthode d'accès à d'autres contrôles n'est pas liée à l'ajout de génériques.
N'hésitez pas à ouvrir un autre sujet à ce sujet cependant

Je ne pense vraiment pas qu'avoir le type de retour défini soit vraiment "fortement typé", cela semble être la moitié de l'implémentation requise, mais c'est un pas dans la bonne direction.

Salut, j'ai publié https://github.com/Quramy/ngx-typed-forms pour contourner ce problème. S'il vous plaît vérifier

@Quramy J'ai essayé d'utiliser votre package il y a plusieurs semaines et si je me souviens bien, cela ne fait pas vraiment beaucoup d'application :(

+1. Je ne peux pas compter le nombre d'instances lorsque j'aurais souhaité qu'il soit mis en œuvre.

Même.
Les formes réactives angulaires sont l'une des fonctionnalités qui bat vraiment tout autre framework. Rendre les formulaires réactifs fortement typés le ferait passer au niveau supérieur, élargissant encore l'écart avec la concurrence :)

cela peut être fait avec les types mappés conditionnels et la récursivité..... les types mappés conditionnels ont juste été fusionnés dans le script dactylographié. Si cela est publié, nous aurons une chance de rendre possible des formulaires dactylographiés solides

Je ne peux pas attendre (

La solution FormBuilder . De plus, les génériques FormGroup<T> , FormControll<T> et FormArray<T> ne peuvent pas être utilisés directement, car ce ne sont que des interfaces qui n'étendent pas les événements AbtractControl<T> . Ce n'était pas suffisant pour notre projet actuel.

Avec ngx-strongly-typed-forms, j'ai maintenant publié moi-même un projet de formulaires puissants
Il rompt un peu avec la rétrocompatibilité, car il n'utilise pas les génériques par défaut. Cela vous oblige donc à donner explicitement à vos contrôles un type quelconque, mais ajoute beaucoup plus de sécurité de type à toutes les autres parties et est compatible avec l'API avec l'implémentation actuelle de @angular/forms.
C'est peut-être une alternative valable jusqu'à ce que cette fonctionnalité soit implémentée dans Angular.

+1 Il s'agit d'une fonctionnalité puissante.

Devrait être mis en œuvre dès que possible)

La façon dont il est censé être codé

Il y a une implémentation intéressante dans ce logiciel open source (AGPL)

https://github.com/concentricsky/badgr-ui/blob/master/src/app/common/util/typed-forms.ts

J'ai utilisé ceci comme solution de contournement temporaire:
J'espère que ça aide

import { FormControl, AbstractControl } from "@angular/forms";
export class NumberControl extends FormControl{
    _value: Number;
    get value(){ 
        return this._value;
    }
    set value(value){
        this._value = Number(value);
    }
}

C'est quelque chose auquel j'ai pensé à quelques reprises dans des projets sur lesquels j'ai travaillé, mais je n'ai pas suffisamment utilisé les proxys JavaScript pour connaître l'impact sur les performances que cela aurait sur tout ce qui observe ces valeurs.

J'ai simplement créé une solution de contournement personnalisée au niveau FormBuilder :

import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
type ExtraProperties = { [key: string]: any } | null;
interface IModelConstructor { new(...args: any[]): any }

@Injectable({
    providedIn: 'root'
})
export class TypedFormBuilder {
    array = this.formBuilder.array;
    control = this.formBuilder.control;
    group = this.formBuilder.group;

    constructor(private formBuilder: FormBuilder) {}

    typedGroup<TGroupModel extends object>(ModelType: IModelConstructor, extraProps?: ExtraProperties): FormGroup & TGroupModel {
        const formGroup: any = this.group({
            ...new ModelType(),
            ...extraProps
        });

        return new Proxy<FormGroup & TGroupModel>(formGroup, {
            get: (target: FormGroup & TGroupModel, propName: string | number | symbol) => {
                if (propName in target) {
                    return target[propName];
                } else {
                    const property = target.get(propName as string);
                    return property && property.value;
                }
            },
            set: (target: FormGroup & TGroupModel, propName: string | number | symbol, value: any, _: any) => {
                if (propName in target) {
                    target[propName] = value;
                } else {
                    target.setValue({ [propName]: value });
                }

                return true;
            }
        });
    }
}

Cette solution n'est certainement pas super raffinée, et il serait probablement préférable que le FormGroup généré accède aux valeurs d'une propriété (telle que myGroup.fields, où "fields" serait le type fourni). Mais cela fournit un typage fort. Pour l'utiliser:

generateFormGroup() {
    this.theGroup = builder.typedGroup<MyModel>(MyModel);
    this.theGroup.someProperty = 5;
}

J'ai acquis de l'expérience au cours des derniers mois avec les formulaires dactylographiés, en utilisant mes dactylographies de formulaires dans mon projet actuel. Il offre une grande valeur lorsque l'on travaille sur un projet avec trois équipes de développeurs et que tout le monde a changé parce que c'était vraiment bon marché de le faire.

Mais je veux discuter, si c'est la bonne décision de simplement mettre des génériques sur l'API actuelle. Lors de la création des types pour tous les contrôles de formulaire, j'ai trouvé de nombreux cas limites et des choses impossibles ou lourdes à saisir, car je pense que le typage statique n'était pas possible à ce moment-là et donc pas l'une des plus grandes préoccupations.
Malheureusement, cela cible la fonctionnalité principale avec AbstractControl#value , qui doit être quelque chose comme DeepPartial<T> , ou AbstractControl#get avec des implémentations différentes pour chaque sous-classe.
Être rétrocompatible perd également une partie de la sécurité des types causée par les cas de chute avec les types any .
Peut-être envisager une nouvelle API pour les formulaires réactifs est-il également une option pour ce problème ?

Donc, c'est ce que j'ai fini par faire pendant qu'une solution réelle se produit.

Avis de non-responsabilité... Je viens de commencer dans Angular, mais assez familier avec Typescript, donc je ne comprends pas tout à fait les formulaires réactifs... voici ce qui a fini par fonctionner pour moi, mais bien sûr ce n'est pas tout à fait complet, j'ai juste tapé FormGroup, mais Je suis sûr que plus de choses devront être tapées à mesure que j'en apprendrai plus sur les formulaires...

import { FormGroup } from '@angular/forms';

export class FormGroupTyped<T> extends FormGroup {
  public get value(): T {
    return this.value;
  }
}

et puis je peux l'utiliser comme ça

import { FormGroupTyped } from 'path/to/your/form-group-typed.model';

interface IAuthForm {
  email: string;
  password: string;
}

const authForm: FormGroupTyped<IAuthForm> = fb.group({
  email: ['', [Validators.required]],
  password: ['', [Validators.required]],
});

const formValues: IAuthForm = this.authForm.value;
const email: string = formValues.email; 
const invalidKeyVar: string = formValues.badBoy; // [ts] Property 'badBoy' does not exist on type 'IAuthForm'. [2339]
const invalidTypeVar: number = formValues.password; // [ts] Type 'string' is not assignable to type 'number'. [2322]

@Toxicable Lol recherchait ce problème exact ! haha

@cafesanu Voici une petite amélioration de votre FormGroup typé pour vérifier le constructeur.

import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormGroup, ValidatorFn } from '@angular/forms';

export class FormGroupTyped<T> extends FormGroup {
  readonly value: T;
  readonly valueChanges: Observable<T>;

  constructor(controls: { [key in keyof T]: AbstractControl; },
              validatorOrOpts?: ValidatorFn | Array<ValidatorFn> | AbstractControlOptions | null,
              asyncValidator?: AsyncValidatorFn | Array<AsyncValidatorFn> | null) {
    super(controls, validatorOrOpts, asyncValidator);
  }

  patchValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void {
    super.patchValue(value, options);
  }

  get(path: Array<Extract<keyof T, string>> | Extract<keyof T, string>): AbstractControl | never {
    return super.get(path);
  }
}

utilisation :

export class SearchModel {
  criteria: string;
}

const searchForm = new FormGroupTyped<SearchModel >({
  criteria: new FormControl('', Validators.required) // OK
  badBoy: new FormControl('', Validators.required)  // TS2345: Object literal may only specify known properties, and 'badBoy' does not exist in type '{ criteria: AbstractControl; }'.
});

J'ai écrit un petit wrapper autour de FromControl qui permet de générer dynamiquement le type de données en fonction des appels à un constructeur : https://github.com/concentricsky/badgr-ui/blob/develop/src/app/common/util/ formulaires-typés.ts

Le type construit dynamiquement garantit que la forme du formulaire correspond à vos attentes, plutôt que d'avoir à pré-déclarer le type d'interface et ensuite espérer que vous effectuez les bons appels pour créer le formulaire.

J'aimerais voir quelque chose de similaire à cela dans Angular à un moment donné.

L'utilisation ressemble à ceci :

// Create a typed form whose type is dynamically constructed by the builder calls
const group = new TypedFormGroup()
    .add("firstName", typedControl("", Validators.required))
    .add("lastName", typedControl("", Validators.required))
    .add(
        "address",
        typedGroup()
            .add("street", typedControl("2557 Kincaid"))
            .add("city", typedControl("Eugene"))
            .add("zip", typedControl("97405"))
    )
    .addArray(
        "items",
        typedGroup()
            .addControl("itemName", "")
            .addControl("itemId", 0)
    )
;

// All these are type checked:
group.value.address.street.trim();
group.controls.firstName.value;
group.untypedControls.firstName.value;
group.value.items[0].itemId;

Salut à tous, au cours des 3 derniers jours, j'expérimentais l' utilisation d'un
Je pense que cela peut être une solution/solution de contournement possible pour votre problème 😉

//BASIC TYPES DEFINED IN @angular/forms + rxjs/Observable
type FormGroup = import("@angular/forms").FormGroup;
type FormArray = import("@angular/forms").FormArray;
type FormControl = import("@angular/forms").FormControl;
type AbstractControl = import("@angular/forms").AbstractControl;
type Observable<T> = import("rxjs").Observable<T>;

type STATUS = "VALID" | "INVALID" | "PENDING" | "DISABLED"; //<- I don't know why Angular Team doesn't define it https://github.com/angular/angular/blob/7.2.7/packages/forms/src/model.ts#L15-L45)
type STATUSs = STATUS | string; //<- string is added only becouse Angular base class use string insted of union type https://github.com/angular/angular/blob/7.2.7/packages/forms/src/model.ts#L196)

//OVVERRIDE TYPES WITH STRICT TYPED INTERFACES + SOME TYPE TRICKS TO COMPOSE INTERFACE (https://github.com/Microsoft/TypeScript/issues/16936)
interface AbstractControlTyped<T> extends AbstractControl {
  // BASE PROPS AND METHODS COMMON TO ALL FormControl/FormGroup/FormArray
  readonly value: T;
  valueChanges: Observable<T>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}

interface FormControlTyped<T> extends FormControl {
  // COPIED FROM AbstractControlTyped<T> BECOUSE TS NOT SUPPORT MULPILE extends FormControl, AbstractControlTyped<T>
  readonly value: T;
  valueChanges: Observable<T>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}
interface FormGroupTyped<T> extends FormGroup {
  // PROPS AND METHODS SPECIFIC OF FormGroup
  //controls: { [P in keyof T | string]: AbstractControlTyped<P extends keyof T ? T[P] : any> };
  controls: { [P in keyof T]: AbstractControlTyped<T[P]> };
  registerControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): AbstractControlTyped<T[P]>;
  registerControl<V = any>(name: string, control: AbstractControlTyped<V>): AbstractControlTyped<V>;
  addControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): void;
  addControl<V = any>(name: string, control: AbstractControlTyped<V>): void;
  removeControl(name: keyof T): void;
  removeControl(name: string): void;
  setControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): void;
  setControl<V = any>(name: string, control: AbstractControlTyped<V>): void;
  contains(name: keyof T): boolean;
  contains(name: string): boolean;
  get<P extends keyof T>(path: P): AbstractControlTyped<T[P]>;
  getRawValue(): T & { [disabledProp in string | number]: any };
  // COPIED FROM AbstractControlTyped<T> BECOUSE TS NOT SUPPORT MULPILE extends FormGroup, AbstractControlTyped<T>
  readonly value: T;
  valueChanges: Observable<T>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}

interface FormArrayTyped<T> extends FormArray {
  // PROPS AND METHODS SPECIFIC OF FormGroup
  controls: AbstractControlTyped<T>[];
  at(index: number): AbstractControlTyped<T>;
  push<V = T>(ctrl: AbstractControlTyped<V>): void;
  insert<V = T>(index: number, control: AbstractControlTyped<V>): void;
  setControl<V = T>(index: number, control: AbstractControlTyped<V>): void;
  getRawValue(): T[];
  // COPIED FROM AbstractControlTyped<T[]> BECOUSE TS NOT SUPPORT MULPILE extends FormArray, AbastractControlTyped<T[]>
  readonly value: T[];
  valueChanges: Observable<T[]>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T>[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T>[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}

Test d'utilisation dans stackblitz - veuillez télécharger le code et l'exécuter dans le VSCode local Je ne sais pas pourquoi sur stackblitz, les ERREURS pour setValue et pathValue ne sont pas correctes...

Sur ce fil twitter je discute avec @IgorMinar de quelques "idées futures" (après V8+)

Tous les commentaires, suggestions et aides sont les bienvenus !

Ma solution s'appelle @ng-stack/forms . L'une des fonctionnalités intéressantes est la détection automatique des types de formulaires.

Donc, pas besoin de faire ça dans vos composants :

get userName() {
  return this.formGroup.get('userName') as FormControl;
}

get addresses() {
  return this.formGroup.get('addresses') as FormGroup;
}

Maintenant, fais ceci :

// Note here form model UserForm
formGroup: FormGroup<UserForm>;

get userName() {
  return this.formGroup.get('userName');
}

get addresses() {
  return this.formGroup.get('addresses');
}

Pour plus d'informations, voir @ng-stack/forms

Hé, nous avons récemment travaillé avec @zakhenry sur 2 choses :

  • Améliorer les types de formulaires
  • Améliorer la façon de gérer les sous-formulaires (au sein des sous-composants)

Nous ne couvrons pas tous les types comme

Si quelqu'un veut jeter un œil à la lib : ngx-sub-form

Salut @maxime1992 J'ai regardé le Controls<T> mais il ne restreint pas le AbstractControl to <T[K]>
Puis-je vous demander pourquoi ? Le laissez-vous « non typé » parce que vous ressentez le besoin de changer le type au moment de l'exécution en utilisant quelque chose comme les méthodes setControl ou registerControl pour redéfinir et modifier le FormControl et peut-être le type associé ?
Parce que mon TypedForms.d.ts est un peu plus strict et "force" le AbstractControlTyped to the type T<P> mais je ne sais pas si avec ce genre de choix appliquer/désactiver quelque chose qui dans l'API ReactiveForms d'origine est autorisé et peut-être utilisé par quelqu'un...

Toute pensée? Un cas réel à considérer ?
Tout commentaire à ce sujet peut m'aider à décider comment modifier les définitions que j'ai créées et les relations publiques sur lesquelles je travaille...
Merci

PS : Excellent travail sur ngx-sub-form 👍 l'idée d'utiliser ControlValueAccessor pour gérer les sous-formulaires est quelque chose que j'expérimentais aussi 😉

Pour @ng-stack/forms, ajout de la prise en charge de la validation typée.

Cours FormControl , FormGroup , FormArray et toutes les méthodes de FormBuilder
accepter "error validation model" comme deuxième paramètre pour un générique :

class ValidationModel {
  someErrorCode: { returnedValue: 123 };
}
const control = new FormControl<string, ValidationModel>('some value');
control.getError('someErrorCode'); // OK
control.errors.someErrorCode; // OK
control.getError('notExistingErrorCode'); // Error: Argument of type '"notExistingErrorCode"' is not...
control.errors.notExistingErrorCode; // Error: Property 'notExistingErrorCode' does not exist...

Par défaut utilisé un type spécial appelé ValidatorsModel .

const control = new FormControl('some value');
control.getError('required'); // OK
control.getError('email'); // OK
control.errors.required // OK
control.errors.email // OK
control.getError('notExistingErrorCode'); // Error: Argument of type '"notExistingErrorCode"' is not...
control.errors.notExistingErrorCode // Error: Property 'notExistingErrorCode' does not exist...

ValidatorsModel contient une liste de propriétés extraites de typeof Validators , et les types de retour attendus :

class ValidatorsModel {
  min: { min: { min: number; actual: number } };
  max: { max: { max: number; actual: number } };
  required: { required: true };
  requiredTrue: { required: true };
  email: { email: true };
  minLength: { minlength: { requiredLength: number; actualLength: number } };
  maxLength: { requiredLength: number; actualLength: number };
  pattern: { requiredPattern: string; actualValue: string };
}

@dmorosinotto Merci beaucoup mais je ne peux pas utiliser
Validators.compose([Validators.required, Validators.maxLength(20), Validators.minLength(3)])

Salut @youssefsharief pouvez-vous me donner plus de détails sur votre problème avec les validateurs ? Quel type d'erreur/problème avez-vous trouvé en utilisant le .d.ts ?

Si vous pouvez poster un exemple de code ou un stackblitz, je le regarderai et j'essaierai d'aider à résoudre le problème si je trouve une solution 😉

Pour @ng-stack/forms ajout de la prise en charge de input[type="file"] .

Voir aussi l' exemple sur stackblitz

J'ai également utilisé une approche "classique" avec l'utilisation de FormData pour télécharger des fichiers, mais il y a plusieurs bénéfices :

  • récupérer automatiquement le nom d'entrée du formulaire, par exemple <input type="file" name="someName"> -> formControl.value avec le nom du champ someName ;
  • prend en charge l'attribut multiple avec un nom de champ correctement défini (notez userpic[] ici)
<input type="file" multiple name="userpic" [formControl]="formControl">

```ts
formData.append('userpic[]', myFileInput.files[0]);
formData.append('userpic[]', myFileInput.files[1]);

- `Validators` have four static methods for files
```ts
import { Validators } from '@ng-stack/forms';

Validators.fileRequired;
Validators.fileMaxSize(1024);
Validators.filesMaxLength(10);
Validators.filesMinLength(2);

@KostyaTretyak d' après ce que j'ai vu jusqu'à présent, votre package semble être une excellente solution pour les problèmes de frappe actuels dans les formulaires réactifs angulaires. Avez-vous déjà pensé à contribuer à Angular lui-même au lieu de faire votre propre implémentation ?

Nous sommes presque à l'angular 8 et n'avons toujours pas de formulaires dactylographiés prêts à l'emploi dans un environnement 100% TypeScript, ce qui me semble assez étrange.

@kroeder , je viens de voir que le problème actuel a été créé il y a plus de deux ans (presque immédiatement après la sortie d'Angular 2), et je vois qu'une Pull Request similaire a été créée il y a 1,5 ans sans fusion...

Mais si @ng-stack/forms sera populaire et compatible avec Angular 8+, je créerai peut-être la Pull Request à l'avenir.

@KostyaTretyak Ça a l' https://github.com/no0x9d/ngx-strongly-typed-forms qui est mentionné plus tôt dans ce fil et créé par @no0x9d En quoi votre bibliothèque diffère-t-elle de cela ?

@ZNS , je ne sais pas, mais après un examen rapide, je pense qu'il n'y a pas de générique pour la validation dans ngx-strongly-typed-forms , ainsi que la détection automatique des types appropriés pour les contrôles de formulaire . J'ai peut-être tort.

@ZNS @KostyaTretyak Bonjour. Comme mentionné ci-dessus, je suis l'auteur de ngx-strongly-typed-forms .
J'ai passé en revue rapidement l'ensemble des fonctionnalités de @ng-stack/forms et je pense qu'il y a quelques petites différences.

Il existe une différence de conception entre presque toutes les solutions ou solutions de contournement et mon projet.
Dans la plupart des cas, l'Angular FormControl d'origine est étendu ou encapsulé par un proxy. Certaines méthodes sont remplacées par d'autres signatures de type et déléguées à la fonction d'origine.
Cela introduit une nouvelle couche avec un impact négligeable sur les performances, mais le plus important est le code qui doit être maintenu et peut introduire des bogues dans votre projet.
À mon avis, cela n'est pas nécessaire pour les vérifications statiques au moment de la compilation. La seule partie d'exécution est le NgModule qui fournit mon FormBuilder typé, qui est en fait le FormBuilder angulaire d'origine. Tout le reste n'est que du code angulaire.

En comparaison directe, je n'ai pas le ValidationModel et la conversion d'objets en FormGroups et de tableaux en FormArrays, mais j'ai apporté quelques modifications avisées à AbstractControl#get pour le rendre plus sûr et avoir des arguments de validation typés.

Avec quelques petits ajouts, mon code peut être rétrocompatible et je pourrais créer une pull request. Mais une demande de tirage similaire est périmée depuis longtemps.
Mais s'il y a des efforts pour obtenir cela dans Angular, je serais heureux d'unir nos forces. D'un autre côté, j'aimerais voir une nouvelle API pour les formulaires mieux conçue pour être strictement dactylographiée. Voir mon commentaire pour plus de détails.

+1 aimerait voir cette fonctionnalité. Quelqu'un y travaille ?

bosse à l'équipe angulaire?

Ils ne se soucient pas des développeurs. Ils ont leur propre backlog, avec un blackjack et des fonctionnalités impressionnantes de réduction de 5% de la taille du paquet.

@Lonli-Lokli

Ils ne se soucient pas des développeurs.

Critiquer est facile. Vous souciez-vous davantage des autres développeurs ?
Je n'ai pas vu de relations publiques de votre part pour améliorer les formulaires ni aucune sorte de commentaire constructif ou de RFC pour faire avancer les choses :man_shruging:

Ils ont leur propre arriéré

Pas question :peur:!
Les gens donnent la priorité aux choses dont l'entreprise _[qui les paie]_ a besoin ?
C'est dommage!
image

des fonctionnalités impressionnantes de réduction de 5 % de la taille du paquet.

Vous parlez clairement d'Ivy et de la très petite différence (actuellement) de taille de paquet.
Ivy est actuellement expérimental et vous devez vous y inscrire. Quelle surprise, les choses ne sont pas encore parfaites ! :en pensant:
Oui, il a été dit qu'Ivy aiderait à réduire la taille du paquet et permettrait aux outils de mieux secouer les arbres sur les applications. Et j'espère que ça viendra ! Pour l'instant, ils ne travaillent qu'en s'assurant qu'il ne casse rien et peuvent plus tard aider à avoir de meilleures informations de débogage, une compilation incrémentielle sur la base des composants plutôt que sur la base des modules, et le tremblement de l'arbre. Mais l'outillage pour faire trembler cet arbre fonctionnera plus tard.

Alors s'il vous plaît, essayez d'être respectueux et évitez de saccager les gens qui vous offrent un framework open source gratuitement. Les choses ne sont pas parfaites, un travail énorme est en cours, oui, il semble que certains problèmes soient laissés de côté, mais ce remaniement était nécessaire et il n'y aura jamais de bon moment pour faire quelque chose d'aussi gros que cela, cela devait juste arriver à un moment donné.

Maintenant, je suis hors de ce débat parce que je ne veux pas monopoliser ce fil en parlant de choses non productives. *s'envole*

@maxime-allex
Il y a beaucoup d'autres PR (386 à ce jour), pensez-vous qu'un autre va changer quelque chose ?
En parlant de ce problème, ce PR lié (https://github.com/angular/angular/pull/20040) n'a toujours pas fusionné.

En parlant de refactoring, Ivy a été mentionné il y a un an. Je sais que quelqu'un peut traiter est une fonctionnalité majeure pour les développeurs, mais personnellement je préfère voir les correctifs sont importants pour autant de développeurs que possible.
https://github.com/angular/angular/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

J'espère toujours qu'Angular est destiné aux développeurs, pas au marketing, et j'espère voir plus de problèmes résolus en fonction de la réaction de la communauté. C'est ma question, quelles sont les priorités ici.

Évidemment, ce problème peut être résolu via une mise à jour de l'API existante, mais, séparément, j'ai créé une proposition pour améliorer le ReactiveFormsModule afin de résoudre un certain nombre de problèmes en suspens, y compris celui-ci.

Parmi les autres problèmes résolus, citons la possibilité de s'abonner aux mises à jour sur n'importe quelle propriété et la possibilité de valider de manière asynchrone un contrôle via un service (plutôt qu'un asyncValidator ). Vous pouvez en savoir plus dans #31963. Les commentaires sont les bienvenus !

Comme promis auparavant , j'ai créé Pull Request . Ce PR ne contient qu'une partie de la fonctionnalité @ng-stack/forms (sans : validation, contrôle de formulaire de détection automatique et support input[file]).

Hé, je voulais partager une mise à jour de l'équipe Angular : nous vous entendons que c'est un gros problème. Nous commencerons bientôt à travailler sur des formulaires plus fortement dactylographiés, ce qui inclura l'examen des PR existants et l'examen à nouveau de tous vos commentaires. Merci à tous d'avoir pris le temps de laisser vos pensées!

Oh!!!!! Sortie !

C'est une très bonne nouvelle, l'équipe Angular, merci !
Dès qu'il y aura une version, je vais déprécier angular-typesafe-reactive-forms-helper .

OUI !!!!

Je suis vraiment enthousiaste!! Merci, l'équipe Angular !!

Pouvons-nous arrêter avec le spam de réaction?
Utilisez les emojis pour les réactions, car ils sont destinés à https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/ - merci.

Comme l'équipe Angular a confirmé travailler sur une forme réactive fortement typée. Je voudrais partager mes raisons de mon implémentation qui utilise fortement le type infer pour extraire le type de valeur afin d'obtenir une expérience de développement typée statique fluide.

1ère approche : commencé avec le type de valeur

Quand j'ai commencé à concevoir le FormGroup, j'ai utilisé un type de valeur simple intuitif comme T .

FormGroup<{ id: string, username: string | null }>

Mais j'ai découvert que lorsque je dois gérer une architecture de formulaire de table complexe, il est très difficile de faire une liaison de type sécurisé dans Angular HTML.

type User = { id: string, username: string | null }
const table: FormArray<User> = new FormArray([{ id: '0', username: 'gaplo917'}])

const row: FormGroup<User> = table.at(0) as FormGroup<User> // you must do a type cast

// NO way to get type-safe binding in Angular HTML
<input [formControl]="table.at(i).controls.id" />

L'implémentation ci-dessus a obligé les développeurs à effectuer une conversion de type personnalisée, ce qui est fastidieux et sujet aux erreurs. À mon humble avis, cela perd complètement la raison d'utiliser une forme réactive fortement typée.

2ème approche : commencé avec un ControlType

Comme l'utilisation d'un type de valeur simple ne fonctionne pas correctement. Je trouve une autre idée d'utiliser un KeyValueControlcomme T et utilisez infer pour extraire le type de valeur de KeyValueControlrécursivement.

export type KeyValueControl<V> = {
  [key in keyof V]: V[key] & AbstractControl
}

export type InferTypedForm<Type> = Type extends TypedFormControl<infer X>
  ? X
  : Type extends Array<TypedFormControl<infer X1>>
  ? X1[]
  : Type extends Array<TypedFormGroup<infer X2>>
  ? Array<InferTypedFormGroup<X2>>
  : Type extends TypedFormGroup<infer X3>
  ? InferTypedFormGroup<X3>
  : Type extends TypedFormArray<infer X4>
  ? Array<InferTypedForm<X4>>
  : never

export type InferTypedFormGroup<T> = { [key in keyof T]: InferTypedForm<T[key]> }

export type InferTypedFormArray<T> = InferTypedForm<T[]>

export class TypedFormGroup<T extends KeyValueControl<T>> extends FormGroup {
  readonly controls: T
  readonly valueChanges: Observable<InferTypedFormGroup<T>>
  readonly statusChanges: Observable<FormStatus>
  readonly value: InferTypedFormGroup<T>
  ...
}

Par conséquent,

interface Foo {
  first: TypedFormControl<string | null>
  last: TypedFormControl<string | null>
}

const table: FormArray<FormGroup<Foo>>

const row: FormGroup<Foo> = table.at(0) // typescript compile OK!

// type-safe binding in Angular HTML, all auto-complete (`id`, `username`) finally works!
<input [formControl]="table.at(0).controls.id" />

Démo en direct

Edit gaplo917/angular-typed-form-codesandbox

Captures d'écran IDE de démonstration en direct

C'est une véritable expérience d'auto-complétion sous des formes complexes.
Screenshot 2020-06-12 at 19 02 10

Formes typées angulaires

100 % compatible avec les modules de formulaire réactifs existants
https://github.com/gaplo917/angular-typed-forms

Les commentaires et améliorations sont les bienvenus. J'espère que les prochains formulaires réactifs fortement typés pourront gérer des modèles de formulaires complexes tels que les sous-formulaires de table et imbriqués.

@IgorMinar , Angular Core Team et membres de la communauté Angular .

Ce long commentaire est entièrement centré sur deux affirmations qui sont mises en évidence par l'auteur du billet « contre les pratiques » et « fortement typée ».

Je suggérerais qu'au lieu d'une approche basée sur l'interface pour une forme réactive fortement typée, nous devrions opter pour une approche basée sur les classes, mais pas de la manière mentionnée dans ng-stacks/forms . Je ne recommanderais pas non plus de modifier la base de code des formulaires angulaires réactifs, car nous pouvons obtenir un formulaire fortement typé sans modifier la base de code de plusieurs manières. Permettez-moi de décrire en détail quels sont les défis de haut niveau que je vois dans l'approche axée sur l'interface et l'approche axée sur les classes est plus intuitive que d'autres et nous obtenons également que l'objet FormGroup est fortement typé. Dans les deux cas, notre objet FormGroup est fortement typé, nous ne perdons pas la puissance du type TypeScript dans l'approche basée sur les classes.

Ma suggestion

Comme nous sommes tous familiers avec les pratiques POO, la classe nous donne plus de flexibilité et de maintenabilité du code. quelques-uns des avantages que je souligne ci-dessous :

  1. Découplez notre code.
  2. Moins de code par rapport à l'approche actuelle ainsi qu'à l'approche basée sur l'interface.
  3. Nous pouvons utiliser des décorateurs personnalisés sur la propriété.
  4. Le code est lisible, maintenable et extensible.
  5. Avec cette approche, nous n'avons pas besoin d'écrire la logique métier dans le modèle, comme si nous mettions le *ngIf avec plusieurs conditions pour afficher les messages d'erreur. Je crois que les modèles ne sont pas destinés à écrire la logique métier.
  6. Beaucoup plus...

Permettez-moi de transformer le code d'interface mentionné ci-dessus en Class et d'appliquer le décorateur de validation sur les propriétés Class . Ici, nous suivons les pratiques du principe de responsabilité unique . Jetez un œil au code ci-dessous :

image

Considérons quelques cas et comparons-les avec l'approche fortement typée basée sur l'interface et la classe qui nous aide à comprendre la différence entre les deux.

1. Créez un groupe de formulaires
Ici, nous utilisons la même instance de FormBuilder et la même méthode de group . Mais le nom du module d'importation sera différent comme ReactiveTypedFormsModule au lieu de ReactiveFormsModule . Créons un FormGroup :
image

Selon le code ci-dessus, la question vient,

L'approche actuelle fonctionnera-t-elle après avoir importé le ReactiveTypedFormsModule ?
Oui, cela fonctionnera, rien ne sera modifié après l'importation du ReactiveTypedFormsModule .

Voyons rapidement les autres cas et concluons ce post.

2. Modifiez la valeur de FormControl
Au lieu d'appeler la méthode setValue , nous pouvons affecter directement la valeur à la propriété Class . Il définira automatiquement la FormControl .

image

3. Exécutez la logique métier en fonction des changements de valeur du FormControl
Au lieu de vous abonner au FormControl ValueChanges , utilisez la puissance de la méthode setter dans TypeScript.

image

4. Convertir la valeur d'entrée
Nous nous concentrons sur Strongly-Typed mais qu'en est-il des valeurs qui proviennent du contrôle d'entrée comme pour la date, nous obtenons la valeur au format String mais nous attendons le format Date en code TS, pour surmonter ce problème, nous créons une directive ou une méthode pour convertir la valeur selon les besoins. Je ne montrerai pas ici le code actuel car c'est un peu maladroit car nous devons créer la directive et faire les choses blah blah.... 😄, donc je voudrais montrer le code Class Driven Approach ici :

@toDate() // this will convert the value before assigning the value in 'dob' property.
dob:Date;

4. Modifiez la valeur de FormGroup FormControl imbriqué
Nous pouvons affecter directement la valeur dans la propriété respective plutôt que d'obtenir l'objet FormGroup imbriqué et appeler la méthode SetValue .

image

5. Ajout de FormGroup dans un FormArray imbriqué
Rien de plus à dire, jetez un œil au code ci-dessous 😄 .

image

Référencer l'objet FormGroup dans le modèle HTML

Code simple . Rien ne sera modifié dans le modèle HTML, mais vous obtiendrez également davantage dans le modèle HTML. Veuillez vous référer au code ci-dessous

<form [formGroup]="user.formGroup">
   <input type="text" formControlName="firstName"/>
   <small >{{user.formGroup.controls.firstName.errorMessage}}</small>
<!--Nested FormGroup-->
   <div [formGroup]="user.address.formGroup">...</div>
<!--Nested FormArray - Skills-->
   <div *ngFor="let skill of user.skills">
       <div [formGroup]="skill.formGroup">...</div>
   </div
</form>

Lien Stackblitz : Exemple de travail de formulaire réactif de type fortement
Exemple sur Github : Formulaire réactif fortement typé

@ajayojha , corrigez-moi si je me trompe, mais il semble que votre commentaire ci-dessus puisse se réduire à ceci :

  1. Les frais généraux associés aux types TypeScript sont mauvais.
  2. Validation d'exécution avec l'aide de décorateurs - c'est bien.
  3. Pourquoi avez-vous besoin de setValue() et de valueChanges() s'il y a des setters/getters ?

Qu'est-ce que je pense:

  1. Écrire des types TypeScript revient à écrire des tests statiques. Quelqu'un peut créer des applications sans tests parce qu'il pense que c'est inutile, mais c'est une mauvaise pratique.
  2. Validation d'exécution avec l'aide de décorateurs - ça peut être une bonne idée, d'accord.
  3. En plus de setValue() il existe également patchValue() , reset() , qui fonctionnent également avec la valeur de formulaire. Remplacer seulement setValue() par un setter rendra le code incohérent. De plus, lorsque nous devons écrire des setters pour chaque propriété de modèle de formulaire, cela ajoutera beaucoup plus de surcharge de code ainsi qu'une surcharge de performances dans le cas des getters. Mélanger les noms de propriétés de modèle de formulaire avec les propriétés de contrôle de formulaire est également une mauvaise idée à mon avis.

Merci, @KostyaTretyak pour vos préoccupations concernant mon commentaire, je serais heureux de répondre à la même chose, veuillez voir ci-dessous mes commentaires en conséquence :).

  1. Les frais généraux associés aux types TypeScript sont mauvais.

Juste pour votre information, l'objet formgroup est fortement typé. Les interfaces sont bonnes mais ne conviennent pas à tous les domaines et je ne dis pas que les types TypeScript sont mauvais, je ne pense pas avoir mentionné quelque part que les types sont mauvais. Ma seule préoccupation concerne Interface parce que nous annulons les pratiques de conception de logiciels avec l'approche Interface ou même je peux dire que nous utilisons l'approche Interface au mauvais endroit et mon approche suggérée est Class. En ce qui concerne ma compréhension à travers l'approche Class, nous ne compromettons pas les avantages des types TypeScript que nous obtenons dans Interface ou même je dirais que nous obtenons plus que l'approche Interface en termes de lisibilité, d'évolutivité et de maintenabilité.

Utilisons-nous la bonne pratique d'interface pour la forme réactive fortement typée ?

Permettez-moi de décrire un peu plus en termes d'interface la mauvaise pratique (selon moi) pour la forme réactive fortement typée.

Les types TypeScript sont bons, mais il n'est pas suggéré que partout nous devions mélanger quoi que ce soit qui ne soit pas conforme aux pratiques du logiciel, comme j'ai clairement mentionné les problèmes avec l'interface. Juste pour penser à mes préoccupations soulignées sur Interface. Permettez-moi de partager mon cas, dans l'une de mes applications d'entreprise qui contient plus de 6 000 composants. Si je choisis l'approche Interface, l'équipe de développement me posera les bonnes questions avant de procéder aux modifications :

  • Quelle interface devons-nous utiliser où, car la même entité est utilisée dans plusieurs composants avec des propriétés différentes. Alors devons-nous créer le composant d'interface séparé? Si oui, alors lisez le deuxième cas.
  • Si nous utilisons l'approche ci-dessus, quelle sera l'approche où je dois utiliser les deux propriétés d'interface dans un ou plusieurs composants. Pour la solution à ce problème, je peux créer une interface supplémentaire et étendre la même. Est-ce bien de créer de plus en plus de fichiers juste pour le shake de Strongly Type Reactive Form ? Qu'en est-il de la maintenabilité ?, je n'ai pas la réponse, sauf pour dire que l'équipe angulaire fournit cette solution donc c'est bien :) (si l'équipe angulaire optera pour l'approche interface).
  • Si nous optons pour l'approche a + b, certains de mes composants nécessitent peu de propriétés, pas tous, alors ? J'ai trois solutions à donner à mon équipe de développement.

    • Créez une nouvelle interface et copiez/collez les propriétés requises dans l'interface nouvellement créée. C'est l'approche la plus sale dans le monde du logiciel. Cela crée beaucoup de problèmes lorsqu'une seule propriété est modifiée du côté du serveur, il est alors difficile de suivre les mêmes zones dans le nombre d'interfaces pour modifier le nom de la propriété.

    • Définissez la propriété nullable. Si je définis la propriété nullable, alors pourquoi dois-je suivre l'approche « B » ?. Encore une fois, je n'ai pas de réponse :( à donner à mon équipe de développement.

    • Ne créez pas d'autre interface, utilisez le type d'utilitaire « partiel » et rendez chaque propriété facultative. En faisant cela, nous perdons l'avantage réel de l'interface. C'est aussi contre les pratiques. Si je dois suivre cela, alors pourquoi je dois suivre l'approche 'A', encore une fois Pas de réponse :).

    • Si je rends toutes/quelques propriétés nullables, qu'en est-il de la lisibilité du code et comment puis-je juger du nombre de propriétés nécessaires pour transmettre la valeur au serveur. Ensuite, je dois vérifier le composant respectif et avoir un aperçu. Problème majeur de lisibilité du code.

Maintenant, réfléchissez simplement aux cas ci-dessus dans une perspective plus large et comparez-les avec les types TypeScript avec interface sur les formulaires réactifs pour les types fortement typés. Je crois que chaque bonne approche permettra de gagner du temps de développement et dans cette approche, désolé de dire que je ne vois aucun avantage selon le principe et les pratiques de conception de logiciels.

  1. Validation d'exécution avec l'aide de décorateurs - c'est bien.

Je suis d'accord avec votre commentaire sur " C'est bien ", l'approche décorateur que nous ne pouvons pas réaliser dans Interface Approach. Je pense que c'est la fonctionnalité la plus puissante de TypeScript, alors pourquoi nous ne pouvons pas utiliser la même chose dans l'approche de la forme réactive et donner à l'équipe de développement le contrôle total des propriétés de leurs objets.

  1. Pourquoi avez-vous besoin de setValue() s'il y a des setters ?

Où j'ai dit que j'avais besoin de 'setValue()' ? Je n'ai pas besoin de setValue et je n'ai pas montré dans l'exemple où j'appelle la méthode setValue dans Class Driven Approach. S'il vous plait corrigez moi si je me trompe.

  1. Écrire des types TypeScript revient à écrire des tests statiques. Quelqu'un peut créer des applications sans tests parce qu'il pense que c'est inutile, mais c'est une mauvaise pratique.

Je ne dis pas que les types TypeScript, c'est comme écrire des tests statiques. Mais je ne suis pas d'accord avec les changements de commit dans les classes de base de forme réactive, je pense que nous pouvons réaliser la même chose sans toucher à la définition de classe. Ici, nous pouvons utiliser la puissance réelle de l'interface que nous n'utilisons pas selon les commits jusqu'à présent, est-ce une bonne pratique que la logique s'exécute depuis si longtemps et nous ajoutons les types génériques en définissant la valeur par défaut de ' quelconque'?
Je pense que nous pouvons réaliser la même chose sans toucher aux classes de base de Reactive Form. Je ne sais pas pourquoi nous ne profitons pas de l'interface au lieu de modifier la définition de la classe de base et de modifier également la spécification.

  1. Validation d'exécution avec l'aide de décorateurs - ça peut être une bonne idée, d'accord.

Bon à savoir que nous sommes tous les deux sur la même page à ce sujet :).

  1. En plus de setValue(), il existe également patchValue(), reset(), qui fonctionnent également avec la valeur de formulaire. Remplacer uniquement setValue() par un setter rendra le code incohérent. De plus, lorsque nous devons écrire des setters pour chaque propriété de modèle de formulaire, cela ajoutera beaucoup plus de surcharge de code ainsi que de performances. Mélanger les noms de propriétés de modèle de formulaire avec les propriétés de contrôle de formulaire est également une mauvaise idée à mon avis.

Permettez-moi de décrire le point ci-dessus dans trois sections appelant la méthode, la surcharge des performances du setter et le mélange des propriétés du modèle de formulaire.

Méthode d'appel : comme prévu, en écrivant cet article, je pensais que quelqu'un pourrait me suggérer d'utiliser la méthode « patchValue » ou « reset ». Encore une fois, je voudrais dire que dans le cas du monde réel, la plupart des équipes de développement utilisent la méthode 'setValue' au lieu de patchValue ou d'autres méthodes (c'est mon expérience selon Angular Application Code Review et Stackoverflow Posts setValue vs patchValue). Mon objectif est simplement d'appeler la méthode pour attribuer la valeur, quelle que soit la méthode que nous appelons.

Performance du passeur : Je suis d'accord avec la déclaration des passeurs qui crée un surcoût de performance. Si tel est le cas, je dirais que nous devons d'abord corriger dans Angular Project car pour lier la forme réactive, Angular Framework utilise la méthode setter dans la classe Control Value Accessor et tant d'autres directives, ce qui crée une surcharge de performances sans utiliser le Approche de classe. Une autre chose que la même approche que nous utilisons également dans plusieurs composants avec le décorateur @Input , nous devons trouver que l'équipe alternative ou angulaire doit fournir une solution différente (je crois) pour surmonter ce type de problème de performances. Donc, je ne pense pas que ce soit une préoccupation majeure. Venons-en maintenant à la question des performances, veuillez comparer avec l'approche existante et l'approche de la méthode setter (c'est facultatif, l'équipe de développement peut opter si elle le souhaite comme la même chose que ChangeDetectionStrategy dans Angular. Veuillez vous référer à l'exemple sur le site de documentation rxweb pour opter ce cas. Jugez que le nombre de fonctions qui appellent lorsque nous souscrivons à la valeur change puis après avoir défini la valeur ou appeler directement la méthode setter.Je pense que c'est beaucoup plus intuitif en termes de moins d'exécution de code par rapport aux changements de valeur, faible taille de construction package, la lisibilité du code et tant d'autres bonnes choses.

Mélanger les propriétés : Alors, quelle est votre opinion, attribuez-vous un nom de propriété FormControl différent du nom de propriété renvoyé par le serveur. Si oui, alors je dirais qu'il s'agit d'un problème majeur dans le code car chaque fois que je dois changer le nom de la propriété avant de la publier sur le serveur, désolé mais je ne préférerais pas tout au long de l'application. Si je considère votre bonne opinion pour mon formulaire de demande qui contient en moyenne plus de 40 champs, alors je dois définir chaque valeur de propriété manuellement, juste pour penser au code dans le composant juste pour secouer l'attribution de la valeur et de la taille de construction de la production. Est-ce une meilleure opinion que l'approche de classe ?
Venons-en maintenant à la solution proposée, nous ne mélangeons pas deux choses en une. Les propriétés FormControl sont différentes et les propriétés de classe sont différentes du type de données respectif. Si vous souhaitez modifier le nom de la propriété comme si le nom de la propriété FormControl est différent de la propriété Data, vous pouvez vous reporter à la documentation du package de formulaire réactif rxweb. Il n'y a donc pas de problème car votre sentiment de mal (mélanger le nom de la propriété avec des noms de contrôle de formulaire) a une solution dans l'approche proposée.

J'espère avoir répondu à toutes vos préoccupations, n'hésitez pas à partager si vous avez d'autres préoccupations à ce sujet :).

Comme je l'ai dit dans mon commentaire précédent, il n'est pas nécessaire de modifier les classes de base de Reactive Form car nous pouvons réaliser les mêmes choses en utilisant la puissance des pratiques du principe de ségrégation d'interface. Voici la solution de formulaire réactif fortement typé de bout en bout avec le package de @rxweb/types . Cela fonctionne bien avec Interface ainsi que l'approche Class :).

À quoi ressemble le code après la mise en œuvre ?

Stackblitz : ouvert
Github : Exemple de formulaire réactif fortement typé basé sur l'interface

Quelqu'un a une suggestion, n'hésitez pas à partager la même chose.

Ainsi, la version 10 d'Angular est maintenant disponible , il s'agit d'une version majeure et les formes apparemment réactives ne seront pas fortement typées avant au moins la version 11 d'Angular. Il faudra donc attendre au moins jusqu'à l'automne pour implémenter cette fonctionnalité.

J'ai une question (ou une proposition ?) concernant la manière dont le modèle de formulaire est construit dans la plupart des suggestions/PR que j'ai vues ici.

Lorsque vous examinez la plupart des bibliothèques et des PR qui tentent de sécuriser le type de formulaires réactifs, vous pouvez voir qu'ils créent un modèle qui ressemble à ceci :

interface Address {
  name: Name;  
}
interface Name {
  firstName: string;
  lastName: string;
}

ceci est ensuite "traduit" en quelque chose comme ceci:

const myForm = new FormGroup<Address>({
  name: new FormGroup<Name>({
    firstName: new FormControl('John'),
    lastName: new FormControl('Doe'),
  })
})

Donc, en termes simplifiés : « S'il s'agit d'un objet, créez un FormGroup pour lui. Si c'est un tableau, créez un FormArray. Et s'il s'agit d'une valeur primitive, créez un FormControl. »

Mais, il y a un problème : vous ne pouvez plus utiliser d'objets dans FormControls.

Les solutions que j'ai vues jusqu'à présent : certaines bibliothèques ne prennent tout simplement pas en charge cela. Et certaines bibliothèques utilisent une sorte de "piratage" pour créer un indice indiquant que vous souhaitez réellement utiliser un FormControl au lieu d'un FormGroup.

Ma question/proposition : qu'est-ce qui s'opposerait à la définition explicite du modèle de formulaire comme suit ?

interface Address {
  name: FormGroup<Name>;  
}
interface Name {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
}

const myForm = new FormGroup<Address>({
  name: new FormGroup<Name>({
    firstName: new FormControl('John'),
    lastName: new FormControl('Doe'),
  })
})

Cela a l'énorme avantage que vous pouvez maintenant mettre des objets dans FormControls. Et cela ne nécessite aucune sorte de "piratage" pour le faire :)

J'ai créé une Codesandbox pour cela, afin que vous puissiez peut-être l'essayer vous-même : https://codesandbox.io/s/falling-grass-k4u50?file=/src/app/app.component.ts

@MBuchalik , oui, c'est la première décision évidente qui vient à l'esprit lorsque vous commencez à travailler sur des "formulaires typés forts". J'ai également commencé par cela, mais cela présente un inconvénient important - la nécessité de créer deux modèles : un pour les contrôles de formulaire, l'autre - pour les valeurs de formulaire.

En revanche, si j'ai bien compris, cette solution nous permettra d'implémenter des « formulaires typés forts » sans casser le chage, et nous n'aurons pas à attendre la sortie de la prochaine version majeure d'Angular. Ici, il est nécessaire de travailler en pratique avec une telle solution pour évaluer si elle présente des lacunes plus critiques que la nécessité de créer deux modèles.

@MBuchalik J'ai partagé les mêmes opinions avec vous et j'ai posé la même question au PR et l'un des contributeurs angulaires ( @KostyaTretyak ) a répondu.

Vous pouvez jeter un œil à la discussion dans le PR :
https://github.com/angular/angular/pull/37389#discussion_r438543624

TLDR ;

Voici quelques problèmes :
Si nous suivons votre approche, nous devons créer deux modèles différents - pour les contrôles de formulaire et pour les valeurs de formulaire.
Le modèle des contrôles de formulaire est plus difficile à lire.

Et j'ai mis en œuvre cette idée pour nos projets il y a six mois qui sont déjà utilisés dans la production. Une expérience de saisie semi-automatique statique COMPLÈTE pour le type de contrôles en HTML angulaire augmente vraiment la productivité de nos développeurs juniors. (avec fullTemplateTypeCheck activé)

J'ai partagé "pourquoi je vais de cette façon" dans le commentaire précédent de ce fil:
https://github.com/angular/angular/issues/13721#issuecomment -643214540

Démo Codesanbox : https://codesandbox.io/s/github/gaplo917/angular-typed-form-codesandbox/tree/master/?fontsize=14&hidenavigation=1&theme=dark

Merci pour vos idées @KostyaTretyak et @gaplo917 ! ??

Si j'ai bien compris, on peut le résumer comme suit.

Si nous ne voulons utiliser qu'un seul modèle, une solution comme celle fournie par @KostyaTretyak pourrait être utilisée. L'inconvénient est cependant que nous ne pouvons plus utiliser d'objets dans FormControls. (Je sais qu'il existe un "hack" qui permettrait cela. Mais alors notre modèle n'est à nouveau pas "propre" ; nous aurions donc encore besoin de 2 modèles.)

Si nous voulons pouvoir utiliser des objets dans FormControls, alors il n'y a probablement (!) aucun moyen d'utiliser une approche comme celle que j'ai illustrée (ou @gaplo917). L'inconvénient est que vous avez essentiellement besoin de 2 modèles. Ou au moins, utilisez des types d'assistance pour "extraire" le modèle de valeur de formulaire.

Il ne nous reste donc plus qu'à nous demander si les objets dans FormControls doivent être possibles ou non. Cela répondrait simplement à la question de savoir laquelle des deux approches est celle à sélectionner. Ou est-ce que j'ai raté quelque chose ?

Merci pour vos idées @KostyaTretyak et @gaplo917 ! ??

Si j'ai bien compris, on peut le résumer comme suit.

Si nous ne voulons utiliser qu'un seul modèle, une solution comme celle fournie par @KostyaTretyak pourrait être utilisée. L'inconvénient est cependant que nous ne pouvons plus utiliser d'objets dans FormControls. (Je sais qu'il existe un "hack" qui permettrait cela. Mais alors notre modèle n'est à nouveau pas "propre" ; nous aurions donc encore besoin de 2 modèles.)

Si nous voulons pouvoir utiliser des objets dans FormControls, alors il n'y a probablement (!) aucun moyen d'utiliser une approche comme celle que j'ai illustrée (ou @gaplo917). L'inconvénient est que vous avez essentiellement besoin de 2 modèles. Ou au moins, utilisez des types d'assistance pour "extraire" le modèle de valeur de formulaire.

Il ne nous reste donc plus qu'à nous demander si les objets dans FormControls doivent être possibles ou non. Cela répondrait simplement à la question de savoir laquelle des deux approches est celle à sélectionner. Ou est-ce que j'ai raté quelque chose ?

@MBuchalik À mon avis, si vous faites confiance au compilateur Typescript et comptez beaucoup sur la fonction "d'inférence de type", vous n'avez pas besoin d'avoir 2 modèles. Notre système interne a plus de 60 formulaires, certains d'entre eux sont très complexes imbriqués avec 3 niveaux de profondeur FormArray-FormGroup-FormArray et nous n'avons pas non plus besoin de modèle explicite pour le type de valeur.

Il n'y a que 2 types de modèles de données avec lesquels jouer :

  • Modèle de demande/réponse de données API
  • Modèle de contrôle de formulaire

99,9% du temps, nous

  1. Créer une encapsulation pour chaque forme complexe
  2. Transformez les données distantes -> données de formulaire
  3. Transformez les données du formulaire -> charge utile distante

l'extrait de code suivant est l'illustration :

interface FooApiData {
   id: string
   age: number
   dob: string | null
   createdAt: string
}

interface FooFormControlType {
  id: TypedFormControl<string>
  age: TypedFormControl<number>

  // calendar view required JS date form control binding
  dob: TypedFormControl<Date | null>
} 

interface FooApiUpdateRequest {
   id: string
   dob: string | null
   age: number
}

class FooForm extends TypedFormGroup<FooFormControlType> {
    constructor(private fb: TypedFormBuilder, private initialValue: FooApiData) {
      super({
          id: fb.control(initialValue.id, Validators.required),
          dob: fb.control(initialValue.dob === null ? new Date(initialValue.dob) : null),
          age: fb.number(initialValue.age, Validators.required)
       })
   }
   toRequestBody(): FooApiUpdateRequest {
       const typedValue = this.value
       return {
          id: typedValue.id,
          dob: typedValue.dob !== null ? moment(typedValue.dob).format('YYYYMMDD') : null,
          age: typedValue.age
       }
   }
}

const apiData = apiService.getFoo()

const form = new FooForm(new TypedFormBuilder(), apiData)

// assume some UI changes the form value
function submit() {
   if(form.dirty && form.valid){
     const payload = form.toRequestBody()
     apiService.updateFoo(payload)
   }
}

PS Il s'agit d'une architecture de flux de données avisée que nous pouvons programmer en toute sécurité dans Typescript

Si nous ne voulons utiliser qu'un seul modèle, une solution comme celle fournie par @KostyaTretyak pourrait être utilisée. L'inconvénient est cependant que nous ne pouvons plus utiliser d'objets dans FormControls. (Je sais qu'il existe un "hack" qui permettrait cela. Mais alors notre modèle n'est à nouveau pas "propre" ; nous aurions donc encore besoin de 2 modèles.)

Ici, il est encore nécessaire d'estimer à quelle fréquence nous devons utiliser un objet pour FormControl . Je pense qu'il peut être estimé quelque part à 5-30%. Autrement dit, si nous utilisons des solutions avec un seul modèle, nous pouvons couvrir 70 à 95 % des cas d'utilisation de FormControl . Pour le reste, fournissez simplement un indice pour TypeScript en tant que type supplémentaire (voir Control<T> , il n'est pas correct de l'appeler un "second modèle") :

interface FormModel {
  date: Control<Date>;
}

Le type Control<T> peut-il être qualifié de hack ? - Oui, c'est probablement un hack, mais pas un hack grossier. Je ne connais aucun cas où ce type ne fonctionne pas comme prévu ou a des effets secondaires.

Oh, je me suis souvenu des effets secondaires avec Control<T> lorsque nous devons utiliser une bibliothèque externe pour le modèle de valeur de formulaire. Dans de tels cas, deux modèles sont vraiment nécessaires :

import { FormBuilder, Control } from '@ng-stack/forms';

// External Form Model
interface ExternalPerson {
  id: number;
  name: string;
  birthDate: Date;
}

const formConfig: ExternalPerson = {
  id: 123,
  name: 'John Smith',
  birthDate: new Date(1977, 6, 30),
};

interface Person extends ExternalPerson {
  birthDate: Control<Date>;
}


const fb = new FormBuilder();
const form = fb.group<Person>(formConfig); // `Control<Date>` type is compatible with `Date` type.

const birthDate: Date = form.value.birthDate; // `Control<Date>` type is compatible with `Date` type.

Mais dans ce code, le surcoût n'est qu'ici :

interface Person extends ExternalPerson {
  birthDate: Control<Date>;
}

Grâce à @ArielGueta , un problème critique avec le type Control<T> est désormais connu. C'est-à-dire que je n'essaierai même pas d'implémenter Control<T> dans les futures Pull Requests pour Angular, comme je l'avais prévu auparavant.

Grâce à @ArielGueta , un problème critique avec le type Control<T> est désormais connu. C'est-à-dire que je n'essaierai même pas d'implémenter Control<T> dans les futures Pull Requests pour Angular, comme je l'avais prévu auparavant.

@KostyaTretyak Ce n'est pas vrai. Le problème critique montre uniquement que votre implémentation "ControlType" est incorrecte.

Une implémentation complète du « Type de contrôle » ne pose aucun problème.

Démo en direct : https://codesandbox.io/s/lucid-bassi-ceo6t?file=/src/app/demo/forms/type -test.ts

Screenshot 2020-07-01 at 00 35 11

C'est-à-dire que je n'essaierai même pas d'implémenter Controldans les futures demandes de tirage pour Angular, comme je l'avais prévu auparavant.

OK, cela signifie donc que votre PR (et les consécutifs) ne prendra probablement jamais en charge les objets dans FormControls ?

@MBuchalik , pour le moment (Angular v10), si nous avons le modèle de formulaire suivant :

interface FormModel {
  date: Date;
}

et si dans notre composant nous voulons accéder à la valeur de la propriété date , nous devons procéder comme suit :

get date() {
  return this.formGroup.get('date') as FormControl;
}
// ...
this.date.value as Date;

Ma demande d'extraction actuelle fournit un générique pour la valeur du formulaire, mais ne fournit pas de type pour le contrôle du formulaire :

get date() {
  return this.formGroup.get('date') as FormControl<Date>;
}
// ...
this.date.value; // Here Date type

@gaplo917 , @MBuchalik , j'ai essayé vos solutions et essayé d'implémenter ma propre solution similaire, mais elles ne fonctionnent pas toutes parfaitement. Cette solution fournit également un ensemble de types pour extraire récursivement les valeurs du modèle de formulaire. Les changements de frais généraux et de rupture sont très importants, voir le projet de relations publiques .

Je doute fortement qu'à l'heure actuelle ces solutions devraient être proposées pour être mises en œuvre dans Angular. C'est-à-dire que pour l'instant, nous devrons utiliser des génériques uniquement pour les valeurs de formulaire, pas pour les types de contrôle de formulaire.

mais ils ne fonctionnent pas tous parfaitement

Je n'ai passé que quelques heures sur mon illustration, donc je ne m'attendais pas à ce qu'elle soit parfaite ;) Pourrais-tu donner des exemples de choses qui ne fonctionnent pas bien ? (Surtout sur des choses qui, de votre point de vue, ne peuvent pas être facilement corrigées ?)

Au fait, une suggestion concernant la rétrocompatibilité : de mon point de vue, il est relativement difficile de rendre l'implémentation complètement rétrocompatible. Pour cette raison, nous pourrions peut-être faire ce qui suit : Nous ne modifions pas du tout les classes FormControl, FormGroup et FormArray. Au lieu de cela, nous en créons de nouveaux qui héritent d'eux (peut-être appelez-les StrictFormControl<T> et StrictFormGroup<T> ou ce que vous voulez). Ce sont ensuite ceux que nous sécurisons. L'avantage : nous sommes sûrs à 100 % qu'aucun changement décisif n'est effectué. :)

quelques heures sur mon illustration, donc je ne m'attendais pas à ce qu'elle soit parfaite ;)

J'ai travaillé avec cette solution pendant quelques jours et je vois à quel point il sera difficile de travailler avec des formulaires.

  1. Tout d'abord, des frais généraux importants et la nécessité d'avoir deux modèles.
  2. Deuxièmement, cette solution n'est pas meilleure en terme de fiabilité que ma solution de type Control<T> , car de la même manière il faut extraire récursivement la valeur du modèle de formulaire.
  3. Travaillez avec des contrôles de formulaire imbriqués. Si nous avons le modèle de formulaire suivant :
interface FormModel {
  one: FormGroup<{two: FormControl<string>}>;
}

Et si nous obtenons formGroup.controls.one.value , TypeScript fournit un indice avec le type conditionnel, pas avec le type {two: string} (comme il se doit). Donc, valeur difficile à lire à partir de l'IDE.

Et si nous obtenons formGroup.controls.one.value, TypeScript fournit un indice avec un type conditionnel, pas avec le type {two: string} (comme il se doit). Donc, valeur difficile à lire à partir de l'IDE.

OK donc juste pour m'assurer que j'ai tout bien compris. Si vous utilisez mon implémentation et écrivez ce qui suit :

interface FormModel {
  one: FormGroup<Two>;
}
interface Two {
  two: FormControl<string>;
}

const myForm = new FormGroup<FormModel>({
  one: new FormGroup<Two>({
    two: new FormControl('')
  })
});

(c'est un peu plus verbeux ;) )

Si je recherche maintenant myForm.controls.one.value cela ressemble à ceci :

grafik

Alors vous dites que, dans cet exemple, "deux" ne devrait pas être facultatif ? Je suppose que ce n'est pas la bonne façon de taper la valeur du formulaire. La valeur du formulaire n'inclut que les champs non désactivés. Donc, de mon point de vue, ce devrait être un partiel récursif. Vous ne pouvez pas savoir au moment de la compilation quels champs seront désactivés et lesquels ne le seront pas.

Alors vous dites que, dans cet exemple, "deux" ne devrait pas être facultatif ?

Quoi? Non.

Mon test de ta solution :

interface FormModel {
  one: FormGroup<{two: FormControl<string>}>;
}

let formGroup: FormGroup<FormModel>;
const some = formGroup.controls.one.value;

Ce que je vois sur codesandbox après le value :

(property) FormGroup<FormGroupControls<{ two: FormControl<string>; }>>.value: PartialFormGroupValue<FormGroupControls<{
    two: FormControl<string>;
}>>

Ici, PartialFormGroupValue fait référence au type conditionnel PartialFormValue .

Ah, ok, je pense que j'ai compris. Donc, vous voulez dire que le type est difficile à lire, n'est-ce pas ? Au départ, je pensais que vous parliez d'un bug ou de quelque chose comme ça.

Eh bien, la plupart des IDE ne présenteront toujours que les suggestions pour les propriétés disponibles une fois que vous aurez continué à taper. Donc, je ne vois pas vraiment de problèmes énormes ici. (Bien sûr, il serait préférable de lire s'il disait simplement {two?: string} . Mais je ne pense pas que ce soit super important. C'est du moins mon opinion.)

Si vous avez implémenté votre Control<T> , comment le supprimeriez-vous ensuite de la saisie de la valeur du formulaire sans faire qc comme je l'ai fait ? Et comment feriez-vous de la valeur du formulaire un partiel récursif sans utiliser un type d'aide ?

Si vous avez mis en œuvre votre contrôle, comment le supprimeriez-vous ensuite de la saisie de la valeur du formulaire sans faire qc comme je l'ai fait ? Et comment feriez-vous de la valeur du formulaire un partiel récursif sans utiliser un type d'aide ?

Dans ce cas, ma solution n'est pas meilleure :

(property) FormGroup<FormGroup<{ two: FormControl<string>; }>>.value: ExtractGroupValue<FormGroup<{
    two: FormControl<string>;
}>>

J'ai donné cet exemple parce que vous l'avez demandé :

Pouvez-vous donner des exemples de choses qui ne fonctionnent pas bien ? (Surtout sur des choses qui, de votre point de vue, ne peuvent pas être facilement corrigées ?)

Au fait, j'ai résolu un problème critique avec Control<T> .

Pour résoudre les problèmes de liaison HTML avec Angular 10 et [formControl] , voici la voie que j'ai empruntée :

Comme indiqué dans un autre numéro (https://github.com/angular/angular/issues/36405#issuecomment-655110082), pour mes formulaires, je crée généralement des classes qui étendent FormGroup pour faciliter la réutilisation et les tests . Avec cette structure, j'ai pu résoudre le problème pour le moment en mettant à jour mon code à partir de quelque chose comme ceci :

class UserFormGroup extends FormGroup {
  constructor() {
    super({
      id: new FormControl(null, Validators.required),
      name: new FormControl(null, Validators.required),
    });
}

Pour ça:

// everything will extend these two
export class EnhancedFormGroup<T extends { [key: string]: AbstractControl }> extends FormGroup
{
  controls!: T;
}
export class EnhancedFormArray<T extends AbstractControl> extends FormArray 
{
  controls!: T[];
}

// reworked form from above
function formDefinition() {
   return {
      id: new FormControl(null, Validators.required),
      name: new FormControl(null, Validators.required),
    };
}

class UserFormGroup extends EnhancedFormGroup<ReturnType<typeof formDefinition>> {
  constructor() {
    super(formDefinition());
}

À ce stade, form.controls affichera correctement son type sous la forme { id: FormControl, name: FormControl } , se liant correctement dans le code HTML, et s'agrégera correctement si le formulaire était plus compliqué avec des groupes de formulaires ou des tableaux imbriqués.

L'utilisation de la fonction formDefinition n'est pas jolie, mais c'était la solution la plus propre que j'ai pu trouver pour éviter la duplication entre la définition du formulaire et le constructeur.

Je pense que vous pourriez mettre FormGroup jour controls type)

Éditer
On dirait que c'est encore plus simple si vous n'avez pas besoin de créer des classes qui étendent FormGroup ; vous pouvez créer une fonction d'assistance qui résout le problème générique :

function createEnhancedFormGroup<T extends { [key: string]: AbstractControl }>(controls: T) {
  return new EnhancedFormGroup<T>(controls);
}

const form = createEnhancedFormGroup({
  id: new FormControl(null, Validators.required),
  name: new FormControl(null, Validators.required),
});

modifier 2
... ou vous pouvez l'intégrer dans la classe FormGroup elle-même ( FormBuilder peut-être ?)

export class EnhancedFormGroup<T extends { [key: string]: AbstractControl }> extends FormGroup
{
  controls!: T;

  static create<T extends { [key: string]: AbstractControl }>(controls: T) {
    return new EnhancedFormGroup<T>(controls);
  }
}

const form = EnhancedFormGroup.create({
  id: new FormControl(null, Validators.required),
  name: new FormControl(null, Validators.required),
});

modifier 3
J'ai étendu les exemples ci-dessus pour inclure la saisie sur les value et j'ai créé un article pour tout résumer :

https://medium.com/youngers-consulting/angular-typed-reactive-forms-22842eb8a181

Ceci est maintenant marqué sur la feuille de route pour le développement futur : https://angular.io/guide/roadmap#better -developer-ergonomics-with-strict-typing-for-angularforms

@pauldraper Pouvez-vous expliquer ce qui a changé par rapport à la feuille de route d'il y a environ 2 mois ? Le seul changement que je vois est le libellé du titre. Mais il est toujours dans la section "Avenir". Comme il y a 2 mois.

@MBuchalik c'est peut-être là depuis 2 mois.

Cette page vous a été utile?
0 / 5 - 0 notes