Angular: Reaktive Formen sind nicht stark typisiert

Erstellt am 30. Dez. 2016  ·  90Kommentare  ·  Quelle: angular/angular

[x] feature request
  • Winkelversion: 2

Reaktive Formulare sollen in komplexen Formularen verwendet werden, aber valueChanges Steuerelements sind Observable<any> , was absolut gegen die bewährten Verfahren für komplexen Code verstößt.

Es sollte eine Möglichkeit geben, stark typisierte Formularsteuerelemente zu erstellen.

forms feature high

Hilfreichster Kommentar

Hey, ich wollte ein Update vom Angular-Team teilen: Wir haben gehört, dass dies ein großer Schmerzpunkt ist. Wir werden in Kürze mit der Arbeit an stärker typisierten Formularen beginnen, die das Durchsehen bestehender PRs und die erneute Überprüfung aller Ihrer Kommentare umfassen. Vielen Dank an alle, die sich die Zeit genommen haben, Ihre Gedanken zu hinterlassen!

Alle 90 Kommentare

verwandt #11279

Dies hat nichts mit #11279 zu tun.

Bitte erklären Sie, warum es nicht zusammenhängt?
Was Sie wollen, ist, dass Abstract Control generisch ist, oder? Nur so kann valueChanges einen Typ haben, der nicht Observable<any> gibt keine andere Möglichkeit, den Typ abzuleiten.
Das ist genau das, was #5404 fragt, was bedeutet, dass dies mit #11279 zusammenhängt
Wenn es eine andere Möglichkeit gibt, dies zu implementieren, ohne AbstractControl zu einem Generikum zu machen, erklären Sie dies bitte.

Die Verwendung von get<Type> wie in #11279 ist definitiv die falsche Lösung. Wenn TypeScript etwas wie Java Unbounded Wildcard hätte, würde get es verwenden und nicht any . Vielleicht kann man mit einer leeren Schnittstelle auf die gleiche Weise etwas machen?

Es gibt auch https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html keyof . Es kann sehr interessant sein, die Features von TypeScript 2.1 zu studieren, um stark typisierte Formularsteuerelemente zu implementieren.

So wie es derzeit ist, glaube ich leider nicht, dass es für große Apps geeignet ist und ich etwas darüber entwerfen muss.

Mir ist gerade aufgefallen, dass in TS 2.2 (https://github.com/Microsoft/TypeScript/wiki/Roadmap#22-february-2017) standardmäßige generische Typen (https://github.com/Microsoft/TypeScript/ Ausgaben/2175) sobald wir dies haben, denke ich, dass es eine gute Idee sein könnte, dieses Problem noch einmal zu überdenken, da wir AbstractControl generisch machen könnten wie AbstractControl<T = any> wobei T der Typ von ist der von valueChanges wäre ein Observable<T> . Es wäre derzeit keine gute Idee, dies zu tun, da dies eine massive bahnbrechende Änderung wäre, aber mit Standard-Generika wird es keine bahnbrechende Änderung sein, es sei denn, ich verstehe sie falsch.

Kleines Update dazu, sieht so aus, als ob Default Generics auf TS2.3 verschoben wurden . Mit der Unterstützung von TS 2.1 durch Angular mit Version 4.0 sollte es also nicht lange dauern, bis sie TS 2.3 unterstützen können.

TypeScript 2.3 mit standardmäßigen generischen Typen ist bereits da, haben wir irgendwelche Pläne, wann die Unterstützung für TS 2.3 in Angular bereit sein wird?

@desfero wartet auf #16707 auf das Upgrade des Builds auf TS2.3

+1 würde diese Funktion gerne sehen. Arbeitet jemand daran?

Dies könnte von Nutzen sein - Angular Typesafe Reactive Forms

Kleines Update dazu:
Gemäß meinem Kommentar hier: https://github.com/angular/angular/pull/16828#issuecomment -337034655
Die Implementierung von Generika in die aktuelle Forms-API ist ohne Breaking Changes nicht möglich.
Also sind entweder Breaking Changes nötig
Oder ein vollständiges Umschreiben von Formularen

Es stellte sich also heraus, dass mein vorheriger Kommentar falsch war.
Ich konnte dies auf der aktuellen Forms-API implementieren, wie Sie hier #20040 sehen können

@Toxicable , das immer noch das Problem hat, dass es nicht in der Lage ist, sicher @rpbeukes hat eine nachgerüstete Möglichkeit, das Objektsymbol grundsätzlich zu verwenden, z. get(obj.person) ohne den String zu verwenden. Das wäre vorzuziehen, nur Rückgabetypen zu haben.

@howiempt

get('person') zum Beispiel verwendet das Symbol selbst nicht wirklich

Keine Ahnung was du damit meinst, welches Symbol meinst du hier?
In meiner Implementierung können Sie so etwas tun wie

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) ohne den String zu verwenden

Dies hat nicht die Möglichkeit, mehrere FormGroups zu durchlaufen.
Während meine Methode die Typen in diesem Szenario nicht ableiten kann, besteht die Idee meiner PR darin, generische Typen hinzuzufügen, ohne Änderungen zu ändern oder neue APIs einzuführen (abgesehen von Generika).

@Toxicable Ich verstehe, dass Ihre Änderung dazu gedacht ist, Dinge nicht zu

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>

Sie alle wären enge Referenzen. Die Nachrüstlösung macht dies auf eine etwas hackige Weise, löst aber auch dieses Problem.

@Toxicable Danke für die PR. Ich freue mich darauf, es zu verwenden :)

Ich stimme @howiempt zu , wenn wir so etwas bekommen, wäre es der erste Preis:

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

Auch hier weiß ich nicht wirklich, wie machbar dies im größeren Rahmen der Dinge ist.
Ich vertraue Ihrem Urteil.

Mach weiter so und danke für die schnelle Antwort.

Ich denke, dass diese Methode des Zugriffs auf andere Steuerelemente nichts mit dem Hinzufügen von Generika zu tun hat.
Du kannst aber gerne ein anderes Thema dazu eröffnen

Ich glaube wirklich nicht, dass das Setzen des Rückgabetyps wirklich "stark typisiert" ist, scheint die Hälfte der erforderlichen Implementierung zu sein, aber es ist ein Schritt in die richtige Richtung.

Hallo, ich habe https://github.com/Quramy/ngx-typed-forms zur Umgehung dieses Problems veröffentlicht. Bitte schaut es euch an 😄

@Quramy Ich habe vor einigen Wochen versucht, Ihr Paket zu verwenden, und wie ich mich erinnere, wird es nicht wirklich

+1. Kann die Anzahl der Instanzen nicht zählen, als ich wünschte, dass es implementiert wurde.

Dasselbe.
Angular Reactive Forms ist eine der Funktionen, die jedes andere Framework auf dem Markt wirklich übertrifft. Reaktive Formulare stark typisiert zu machen, würde es auf die nächste Stufe bringen und den Abstand zur Konkurrenz weiter vergrößern :)

dies kann mit bedingten zugeordneten Typen und Rekursion erfolgen ..... bedingte zugeordnete Typen wurden gerade in Typoskript zusammengeführt. Wenn dies veröffentlicht wird, haben wir die Chance, stark typisierte Formen zu ermöglichen

Kann es kaum erwarten (

Die @Quramy- Lösung FormBuilder . Auch generische FormGroup<T> , FormControll<T> und FormArray<T> können nicht direkt verwendet werden, da es sich nur um Schnittstellen handelt, die AbtractControl<T> nicht eventerweitern. Dies war für unser aktuelles Projekt nicht ausreichend.

Mit ngx-strongly-typed-forms habe ich nun selbst ein Strong-
Es bricht ein bisschen mit der Abwärtskompatibilität, da es keine Standardgenerika verwendet. Es zwingt Sie also, Ihren Steuerelementen explizit einen Typ von any zuzuweisen, fügt jedoch allen anderen Teilen viel mehr Typsicherheit hinzu und ist API-kompatibel mit der aktuellen Implementierung von @angular/forms.
Vielleicht ist dies eine gültige Alternative, bis diese Funktion in Angular implementiert wird.

+1 Dies ist eine leistungsstarke Funktion..

Sollte so schnell wie möglich implementiert werden)

So wie es codiert werden soll

Es gibt eine interessante Implementierung in dieser Open-Source-Software (AGPL)

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

Ich habe dies als vorübergehenden Workaround verwendet:
Ich hoffe es hilft

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);
    }
}

Darüber habe ich in Projekten, an denen ich gearbeitet habe, einige Male nachgedacht, aber ich habe JavaScript-Proxys nicht genug verwendet, um die Auswirkungen auf die Leistung zu kennen, die dies auf die Einhaltung dieser Werte haben würde.

Ich habe einfach einen benutzerdefinierten Workaround auf FormBuilder-Ebene erstellt:

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;
            }
        });
    }
}

Diese Lösung ist definitiv nicht auf Hochglanz poliert, und es wäre wahrscheinlich am besten, wenn die von FormGroup generierte Zugriff auf die Werte einer Eigenschaft (wie myGroup.fields, wobei "fields" der bereitgestellte Typ wäre) wäre. Aber dies bietet starke Typisierung. Um es zu benutzen:

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

Ich habe in den letzten Monaten Erfahrungen mit typisierten Formularen gesammelt, indem ich meine Formulareingaben in meinem aktuellen Projekt verwendet habe. Es bietet einen großen Wert, wenn man an einem Projekt mit drei Entwicklerteams arbeitet und alle gewechselt haben, weil es wirklich billig war.

Aber ich möchte diskutieren, ob es die richtige Entscheidung ist, nur Generika auf die aktuelle API zu setzen. Beim Erstellen der Typen für alle Formularsteuerelemente habe ich viele Randfälle und Dinge gefunden, die unmöglich oder umständlich zu tippen sind, weil ich denke, dass statisches Tippen zu diesem Zeitpunkt nicht möglich war und daher nicht eines der größten Probleme ist.
Leider zielt dies mit AbstractControl#value auf die Hauptfunktionalität ab, die etwa DeepPartial<T> oder AbstractControl#get mit unterschiedlichen Implementierungen für jede Unterklasse sein muss.
Durch die Abwärtskompatibilität geht auch ein Teil der Typsicherheit verloren, die durch Fall-Through-Fälle mit any Typen verursacht wird.
Vielleicht kommt für dieses Problem auch eine neue API für reaktive Formulare in Betracht?

Das ist es, was ich letztendlich getan habe, während eine tatsächliche Lösung stattfindet.

Haftungsausschluss... Ich habe gerade mit Angular angefangen, bin aber mit Typescript ziemlich vertraut, daher verstehe ich reaktive Formulare nicht vollständig... hier ist, was für mich funktioniert hat, aber natürlich ist es nicht vollständig, ich habe nur FormGroup eingegeben, aber Ich bin sicher, dass mehr eingegeben werden muss, wenn ich mehr über die Formulare lerne ...

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

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

und dann kann ich es so verwenden

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 hat genau nach diesem Problem gesucht! Haha

@cafesanu Hier ist eine kleine Verbesserung Ihrer eingegebenen FormGroup, um den Konstruktor zu überprüfen.

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);
  }
}

Verwendung :

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; }'.
});

Ich habe einen kleinen Wrapper um FromControl geschrieben, der es ermöglicht, den Datentyp basierend auf Aufrufen an einen Builder dynamisch zu generieren: https://github.com/concentricsky/badgr-ui/blob/develop/src/app/common/util/ typisierte-forms.ts

Der dynamisch konstruierte Typ stellt sicher, dass die Form des Formulars Ihren Erwartungen entspricht, anstatt den Schnittstellentyp vorab deklarieren zu müssen und dann zu hoffen, dass Sie die richtigen Aufrufe zum Erstellen des Formulars ausführen.

Ich würde gerne irgendwann etwas Ähnliches in Angular sehen.

Die Verwendung sieht so aus:

// 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;

Hallo zusammen, in den letzten 3 Tagen habe ich mit einem d.ts gist experimentiert, um eine strengere Definition für ReactiveForms-Klassen zu definieren und eine neue Typed-Schnittstelle zu erstellen, die mit der ursprünglichen Angular-Klasse kompatibel ist.
Ich denke, dass dies eine mögliche Lösung/Umgehung für dein Problem sein könnte 😉

//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;
}

Nutzungstests in Stackblitz - bitte Code herunterladen und in lokalem VSCode ausführen Ich weiß nicht, warum auf Stackblitz die FEHLER für setValue und pathValue nicht korrekt sind ...

In diesem Twitter-Thread diskutiere ich mit @IgorMinar einige "Zukunftsideen" (nach V8+)

Kommentare, Anregungen und Hilfe sind sehr willkommen !

Meine Lösung heißt @ng-stack/forms . Eine der interessanten Funktionen ist die automatische Erkennung von Formulartypen.

Sie müssen dies also nicht in Ihren Komponenten tun:

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

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

Mach das jetzt:

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

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

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

Weitere Informationen finden Sie unter @ng-stack/forms

Hey, wir haben in letzter Zeit mit

  • Formulartypen verbessern
  • Verbesserung der Verwaltung von Unterformularen (innerhalb von Unterkomponenten)

Wir decken nicht alle Typen wie @dmorosinotto ab, aber wir haben einige Sicherheiten, die beim Erstellen von Refactors definitiv hilfreich sind (sowohl für ts als auch für html :tada:).

Wenn jemand einen Blick in die lib werfen möchte:

Hallo @maxime1992 Ich habe mir das Controls<T> aber es strengt nicht das AbstractControl to <T[K]>
Kann ich dich fragen warum? Lassen Sie es "untypisiert", weil Sie den Typ zur Laufzeit ändern müssen, indem Sie so etwas wie setControl- oder registerControl-Methoden verwenden, um das FormControl und möglicherweise den zugehörigen Typ neu zu definieren und zu ändern?
Da mein TypedForms.d.ts etwas strenger ist und die AbstractControlTyped to the type T<P> "erzwingt", weiß ich nicht, ob mit dieser Art von Auswahl etwas erzwungen / deaktiviert wird, das in der ursprünglichen ReactiveForms-API erlaubt ist und möglicherweise von verwendet wird jemand...

Jeder Gedanke? Irgendein realer Fall zu berücksichtigen?
Alle Kommentare dazu können mir bei der Entscheidung helfen, wie ich die Definitionen, die ich erstellt habe, und die PR, an denen ich arbeite, ändern kann...
Danke

PS: Tolle Arbeit an ngx-sub-form 👍 die Idee, ControlValueAccessor zu verwenden, um Unterformulare zu behandeln, ist etwas, das ich auch experimentiert habe 😉

Für @ng-stack/forms wurde Unterstützung für die typisierte Validierung hinzugefügt.

Klassen FormControl , FormGroup , FormArray und alle Methoden von FormBuilder
Akzeptieren Sie "Fehlervalidierungsmodell" als zweiten Parameter für ein Generic:

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...

Standardmäßig wird ein spezieller Typ namens 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 enthält eine Liste von Eigenschaften, die aus typeof Validators extrahiert wurden, und erwartete Rückgabetypen:

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 Vielen Dank, aber ich kann es nicht verwenden
Validators.compose([Validators.required, Validators.maxLength(20), Validators.minLength(3)])

Hallo @youssefsharief, kannst du mir mehr Details zu deinem Problem mit Validatoren geben? Welche Art von Fehler/Problem haben Sie bei der Verwendung der .d.ts-Datei gefunden?

Wenn Sie einen Beispielcode oder einen Stackblitz posten können, werde ich mir das ansehen und versuchen, das Problem zu lösen, wenn ich eine Lösung gefunden habe 😉

Für @ng-stack/forms Unterstützung für input[type="file"] hinzugefügt.

Siehe auch Beispiel auf stackblitz

Ich habe auch den "klassischen" Ansatz verwendet, indem ich FormData zum Hochladen von Dateien verwendet habe, aber es gibt mehrere Vorteile:

  • automatisch den Namen der Formulareingabe abrufen, zB <input type="file" name="someName"> -> formControl.value mit someName Feldname;
  • Attribut multiple mit richtig gesetztem Feldnamen unterstützen (hier userpic[] beachten)
<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 von dem, was ich bisher gesehen habe, scheint Ihr Paket eine großartige Lösung für die aktuellen Tippprobleme in Angular Reactive Forms zu sein. Haben Sie schon einmal darüber nachgedacht, selbst zu Angular beizutragen, anstatt Ihre eigene Implementierung durchzuführen?

Wir sind fast in Angular 8 und haben immer noch keine vorgefertigten typisierten Formulare in einer 100% TypeScript-Umgebung, was sich für mich ziemlich seltsam anfühlt.

@kroeder , ich sehe gerade, dass die aktuelle Ausgabe vor mehr als zwei Jahren erstellt wurde (fast unmittelbar nach der Veröffentlichung von Angular 2), und ich sehe, dass ein ähnlicher Pull Request vor 1,5 Jahren ohne Zusammenführung erstellt wurde ...

Aber wenn @ng-stack/forms populär und kompatibel mit Angular 8+ sein wird, werde ich vielleicht in Zukunft den Pull Request erstellen.

@KostyaTretyak Klingt großartig :) Ich habe https://github.com/no0x9d/ngx-strongly-typed-forms verwendet, das zuvor in diesem Thread erwähnt und von @no0x9d erstellt wurde. Wie unterscheidet sich Ihre Bibliothek davon?

@ZNS , ich weiß es nicht, aber nach einer kurzen Überprüfung denke ich, dass es in ngx-strongly-typed-forms kein Generikum für die Validierung gibt, ebenso wie die Option Geeignete Typen für Formularsteuerelemente automatisch erkennen . Vielleicht bin ich falsch.

@ZNS @KostyaTretyak Hallo zusammen. Wie oben erwähnt, bin ich der Autor von ngx-strongly-typed-forms .
Ich habe eine kurze Überprüfung des Funktionsumfangs von @ng-stack/forms und denke, dass es einige kleine Unterschiede gibt.

Es gibt einen konzeptionellen Unterschied zwischen fast allen Lösungen bzw. Workarounds und meinem Projekt.
In den meisten Fällen wird das ursprüngliche Angular FormControl von einem Proxy erweitert oder umhüllt. Einige Methoden werden mit anderen Typsignaturen überschrieben und an die ursprüngliche Funktion delegiert.
Dadurch wird eine neue Ebene mit vernachlässigbaren Leistungseinbußen eingeführt, aber wichtiger ist Code, der gewartet werden muss und Fehler in Ihrem Projekt verursachen kann.
Meiner Meinung nach ist dies für statische Prüfungen zur Kompilierzeit unnötig. Der einzige Laufzeitteil ist das NgModule, das meinen typisierten FormBuilder bereitstellt, der tatsächlich der ursprüngliche Angular FormBuilder ist. Alles andere ist nur Angular-Code.

Im direkten Vergleich habe ich nicht das ValidationModel und die Konvertierung von Objekten zu FormGroups und Arrays zu FormArrays, aber ich habe einige eigenwillige Änderungen an AbstractControl#get , um es typsicherer zu machen und typisierte Validator-Argumente zu haben.

Mit einigen kleinen Ergänzungen kann mein Code abwärtskompatibel sein und ich könnte einen Pull-Request erstellen. Aber ein ähnlicher Pull-Request ist lange Zeit veraltet.
Aber wenn es Bemühungen gibt, dies in Angular zu bekommen, würde ich mich freuen, meine Kräfte zu bündeln. Auf der anderen Seite würde ich gerne eine neue API für Formulare sehen, die besser für die strikte Typisierung ausgelegt ist. Details finden Sie in meinem Kommentar .

+1 würde diese Funktion gerne sehen. Arbeitet jemand daran?

Stoß zum eckigen Team?

Sie interessieren sich nicht für Entwickler. Sie haben ihren eigenen Rückstand, mit Blackjack und fantastischen Funktionen zur Reduzierung der Paketgröße um 5%.

@Lonli-Lokli

Sie interessieren sich nicht für Entwickler.

Kritisieren ist einfach. Interessieren Sie sich mehr für andere Entwickler?
Ich habe weder eine PR von dir gesehen, um Formulare zu verbessern, noch irgendeine Art von konstruktivem Kommentar oder RFC, um die Dinge voranzubringen :man_shrugging:

Sie haben ihren eigenen Rückstand

Auf keinen Fall :ängstlich:!
Die Leute priorisieren Dinge, die das Unternehmen _[wer bezahlt sie]_ braucht?
Schade!
image

tolle-5%-verringere-Paketgröße-Funktionen.

Du sprichst eindeutig von Ivy und dem (derzeit) sehr kleinen Unterschied in der Bundle-Größe.
Ivy ist derzeit experimentell und Sie müssen sich anmelden. Was für eine Überraschung, die Dinge sind noch nicht perfekt! :Denken:
Ja, es wurde gesagt, dass Ivy dazu beitragen wird, die Bundle-Größe zu reduzieren und es Tools ermöglicht, die Apps besser zu schütteln. Und das kommt hoffentlich! Im Moment arbeiten sie nur daran, um sicherzustellen, dass nichts kaputt geht und können später helfen, bessere Debug-Informationen, inkrementelle Kompilierung auf Komponenten- statt auf Modulbasis und Tree Shaking zu haben. Aber Werkzeuge, um dieses Baumzittern zu erhalten, werden später funktionieren.

Versuchen Sie also, respektvoll zu sein und vermeiden Sie es, Leute zu verwüsten, die Ihnen ein Open-Source-Framework kostenlos zur Verfügung stellen. Die Dinge sind nicht perfekt, riesige Arbeit ist im Gange, ja, es fühlt sich an, als ob einige Probleme zurückgelassen wurden, aber diese Überarbeitung war erforderlich und es wird nie eine gute Zeit geben, etwas so Großes zu machen, es musste einfach irgendwann passieren.

Jetzt bin ich aus dieser Debatte raus, weil ich diesen Thread nicht monopolisieren möchte, in dem es um nicht produktive Dinge geht. *fliegt weg*

@maxime-allex
Es gibt noch viele andere PRs (derzeit 386), glauben Sie, dass eine weitere etwas ändern wird?
Apropos Problem, diese zugehörige PR (https://github.com/angular/angular/pull/20040) ist immer noch nicht zusammengeführt.

Apropos Refactoring, Ivy wurde vor einem Jahr erwähnt. Ich weiß, dass jemand behandeln kann, ist ein wichtiges Feature für Entwickler, aber ich persönlich bevorzuge es, Fixes für so viele Entwickler wie möglich wichtig zu sehen.
https://github.com/angular/angular/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

Ich hoffe immer noch, dass Angular für Entwickler gedacht ist, nicht für das Marketing, und hoffe, dass mehr Probleme aufgrund der Reaktion der Community geschlossen werden. Das ist meine Frage, was sind die Prioritäten hier.

Dieses Problem kann natürlich durch ein Update der bestehenden API behoben werden, aber separat habe ich einen Vorschlag zur Verbesserung des ReactiveFormsModule erstellt, um eine Reihe noch offener Probleme zu beheben, einschließlich dieses.

Einige andere behandelte Probleme umfassen die Möglichkeit, Updates für jede Eigenschaft zu abonnieren und ein Steuerelement asynchron über einen Dienst zu validieren (anstelle eines asyncValidator ). Weitere Informationen finden Sie in #31963. Rückmeldungen sind willkommen!

Wie ich bereits versprochen habe , habe ich Pull Request erstellt . Diese PR enthält nur einen Teil der @ng-stack/forms Funktion (ohne: Validierung, automatische Formularerkennung und Unterstützung von Eingabe[Datei]).

Hey, ich wollte ein Update vom Angular-Team teilen: Wir haben gehört, dass dies ein großer Schmerzpunkt ist. Wir werden in Kürze mit der Arbeit an stärker typisierten Formularen beginnen, die das Durchsehen bestehender PRs und die erneute Überprüfung aller Ihrer Kommentare umfassen. Vielen Dank an alle, die sich die Zeit genommen haben, Ihre Gedanken zu hinterlassen!

Oh!!!!! Aussteigen!

Das sind sehr gute Nachrichten, Angular-Team, danke!
Sobald es eine Veröffentlichung gibt, werde ich angle-typesafe-reactive-forms-helper als veraltet

JASS!!!!

Ich bin so aufgeregt!! Danke, Angular-Team!!

Können wir mit dem Reaktions-Spam aufhören?
Verwenden Sie die Emojis für Reaktionen, da sie für https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/ gedacht sind - danke.

Wie das Angular-Team bestätigt hat, arbeitet es an einer stark typisierten Reactive Form. Ich möchte meine Gründe für meine Implementierung mitteilen, die den Typ infer stark verwenden , um den Werttyp zu extrahieren, um eine reibungslose statische typisierte Entwicklung zu erzielen.

1. Ansatz: Begonnen mit dem Werttyp

Als ich anfing, die FormGroup zu entwerfen, habe ich einen intuitiven einfachen Werttyp als T .

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

Aber ich habe festgestellt, dass es sehr schwierig ist, eine typsichere Bindung in Angular HTML durchzuführen, wenn ich mit einer komplexen Tabellenformulararchitektur umgehen muss.

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" />

Die obige Implementierung erforderte von Entwicklern eine benutzerdefinierte Typumwandlung, die mühsam und fehleranfällig ist. IMHO verliert dies völlig den Grund, stark typisierte Reactive Form zu verwenden.

2. Ansatz: Gestartet mit einem ControlType

Da die Verwendung eines einfachen Werttyps nicht reibungslos funktioniert. Mir fällt eine andere Idee ein, ein KeyValueControl zu verwendenals T und verwenden Sie infer um den Werttyp aus dem KeyValueControl zu extrahierenrekursiv.

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>
  ...
}

Infolge,

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" />

Live-Demo

Edit gaplo917/angular-typed-form-codesandbox

Live-Demo-IDE-Screenshots

Das ist ein echtes Auto-Complete-Erlebnis in komplexer Form.
Screenshot 2020-06-12 at 19 02 10

Angular typisierte Formulare

100 % kompatibel zu bestehenden Reactive Form Modulen
https://github.com/gaplo917/angular-typed-forms

Kommentare und Verbesserungen sind willkommen. Ich hoffe, dass die kommenden stark typisierten reaktiven Formulare komplexe Formularmodelle wie Tabellen- und verschachtelte Unterformulare verarbeiten können.

@IgorMinar , Angular Core Team und Mitglieder der Angular Community .

Dieser lange Kommentar konzentriert sich vollständig auf zwei Aussagen, die vom Ticketautor „ gegen die Praktiken “ und „ stark typisiert “ hervorgehoben werden.

Ich würde vorschlagen, anstelle eines schnittstellenbasierten Ansatzes für Strongly Typed Reactive Form den klassenbasierten Ansatz zu wählen, jedoch nicht so, wie es in den ng-stacks/forms erwähnt wird . Ich würde auch nicht empfehlen, die Codebasis von Angular Ractive Forms zu ändern, da wir eine stark typisierte Form erreichen können, ohne die Codebasis auf viele Arten zu ändern. Lassen Sie mich im Detail beschreiben, was die übergeordneten Herausforderungen sind, die ich im schnittstellengesteuerten Ansatz sehe, und der klassengesteuerte Ansatz ist intuitiver als andere und wir erhalten auch, dass das FormGroup-Objekt stark typisiert ist. In beiden Fällen ist unser FormGroup-Objekt stark typisiert, wir verlieren die Leistungsfähigkeit des TypeScript-Typs im klassenbasierten Ansatz nicht.

Mein Vorschlag

Da wir alle mit OOP-Praktiken vertraut sind, bietet uns die Klasse mehr Flexibilität und Wartbarkeit des Codes. Einige der Vorteile, die ich im Folgenden hervorhebe:

  1. Entkoppeln Sie unseren Code.
  2. Weniger Code im Vergleich zum aktuellen Ansatz sowie schnittstellengesteuerter Ansatz.
  3. Wir können benutzerdefinierte Dekorateure auf dem Grundstück verwenden.
  4. Der Code ist lesbar, wartbar und erweiterbar.
  5. Bei diesem Ansatz müssen wir die Geschäftslogik nicht in die Vorlage schreiben, da wir *ngIf mit mehreren Bedingungen zum Anzeigen der Fehlermeldungen versehen. Ich glaube, die Vorlagen sind nicht zum Schreiben der Geschäftslogik gedacht.
  6. Viel mehr...

Lassen Sie mich den oben erwähnten Interface-Code in Class umwandeln und den Validierungs-Dekorator auf die Class Eigenschaften anwenden. Hier folgen wir den Grundsätzen der

image

Betrachten wir einige Fälle und vergleichen sie mit dem Interface- und Class Driven Strongly-Typed Approach, der uns hilft, den Unterschied in beiden zu verstehen.

1. Erstellen Sie eine Formulargruppe
Hier verwenden wir dieselbe Instanz von FormBuilder und dieselbe Methode von group . Aber der Name des Importmoduls wird anders sein wie ReactiveTypedFormsModule anstelle von ReactiveFormsModule . Lassen Sie uns ein FormGroup erstellen:
image

Gemäß dem obigen Code kommt die Frage,

Funktioniert der aktuelle Ansatz nach dem Importieren von ReactiveTypedFormsModule ?
Ja, es wird funktionieren, nach dem Importieren von ReactiveTypedFormsModule wird nichts geändert.

Sehen wir uns schnell die anderen Fälle an und schließen diesen Beitrag ab.

2. Ändern Sie den Wert von FormControl
Anstatt die Methode setValue aufzurufen, können wir den Wert direkt der Eigenschaft Class zuweisen. Es wird automatisch den FormControl Wert festlegen.

image

3. Ausführen von Geschäftslogik basierend auf Wertänderungen des FormControl
Anstatt FormControl ValueChanges abonnieren, verwenden Sie die Leistungsfähigkeit der Methode setter in TypeScript.

image

4. Konvertieren Sie den Eingabewert
Wir konzentrieren uns auf Strongly-Typed, aber was ist mit den Werten, die aus dem Eingabesteuerelement kommen, wie für das Datum, erhalten wir den Wert im Format String , erwarten jedoch das Format Date im TS-Code, Um dieses Problem zu lösen, erstellen wir eine Direktive oder Methode, um den Wert nach Bedarf umzuwandeln. Ich werde den aktuellen Code hier nicht zeigen, da er ein bisschen umständlich ist, weil wir die Direktive erstellen und die bla-bla-Dinge machen müssen.... 😄, Ich möchte also hier den Code des klassengesteuerten Ansatzes zeigen:

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

4. Ändern Sie den Wert von Nested FormGroup FormControl
Wir können den Wert direkt in der jeweiligen Eigenschaft zuweisen, anstatt das Nested FormGroup-Objekt abzurufen und die Methode SetValue aufzurufen.

image

5. Hinzufügen von FormGroup in Nested FormArray
Nicht mehr zu sagen, schau dir den folgenden Code an 😄 .

image

Verweisen Sie auf das FormGroup-Objekt in der HTML-Vorlage

Einfacher Code 😄 . An der HTML-Vorlage wird nichts geändert, aber Sie erhalten auch mehr in der HTML-Vorlage. Bitte beachten Sie den folgenden Code

<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>

Stackblitz Link : stark typreaktive Formulare
Beispiel auf Github : Strongly Typed Reactive Form

@ajayojha , korrigieren Sie mich, wenn ich falsch

  1. Overhead, der mit TypeScript-Typen verbunden ist, ist schlecht.
  2. Laufzeitvalidierung mit Hilfe von Dekoratoren - das ist gut.
  3. Warum brauchen Sie setValue() und valueChanges() wenn es Setter/Getter gibt?

Was denke ich:

  1. Das Schreiben von TypeScript-Typen ist wie das Schreiben statischer Tests. Jemand kann Anwendungen ohne Tests erstellen, weil er dies für unnötig hält, aber es ist eine schlechte Praxis.
  2. Laufzeitvalidierung mit Hilfe von Dekoratoren - das kann eine gute Idee sein, stimme zu.
  3. Außer setValue() gibt es noch patchValue() , reset() , die auch mit Formularwert arbeiten. Wenn Sie nur setValue() durch einen Setter ersetzen, wird der Code inkonsistent. Wenn wir Setter für jede Eigenschaft des Formularmodells schreiben müssen, wird es außerdem viel mehr Code-Overhead sowie Performance-Overhead im Fall von Gettern hinzufügen. Das Mischen von Formularmodell-Eigenschaftsnamen mit Formularsteuerelement-Eigenschaften ist meiner Meinung nach ebenfalls eine schlechte Idee.

Danke, @KostyaTretyak für deine Bedenken zu meinem Kommentar, ich beantworte gerne gleich, siehe unten meine Kommentare entsprechend :).

  1. Overhead, der mit TypeScript-Typen verbunden ist, ist schlecht.

Nur zu Ihrer Information ist das formgroup-Objekt stark typisiert. Schnittstellen sind gut, aber nicht für jeden Bereich geeignet und ich sage nicht, dass TypeScript-Typen schlecht sind, ich glaube nicht, dass ich irgendwo erwähnt habe, dass die Typen schlecht sind. Meine einzige Sorge gilt Interface, weil wir die Software-Design-Praktiken mit dem Interface-Ansatz außer Kraft setzen, oder ich kann sogar sagen, dass wir den Interface-Ansatz an der falschen Stelle verwenden und mein vorgeschlagener Ansatz ist Klasse. Soweit ich das verstehe, gehen wir durch den Klassenansatz keine Kompromisse in Bezug auf die Vorteile von TypeScript-Typen ein, die wir in Interface erhalten, oder ich würde sogar sagen, dass wir in Bezug auf Lesbarkeit, Skalierbarkeit und Wartbarkeit mehr als den Interface-Ansatz erhalten.

Verwenden wir die richtige Schnittstelle für stark typisierte reaktive Form?

Lassen Sie mich ein wenig mehr in Bezug auf die Schnittstelle beschreiben, ist die schlechte Praxis (wie ich) für stark typisierte reaktive Form.

TypeScript-Typen sind gut, aber es wird nicht vorgeschlagen, dass wir überall alles durcheinander bringen müssen, was nicht den Software-Praktiken entspricht. Wie ich die Probleme mit der Schnittstelle klar erwähnt habe. Um nur an meine hervorgehobenen Bedenken bezüglich der Schnittstelle zu denken. Lassen Sie mich meinen Fall teilen, in einer meiner Unternehmensanwendungen, die mehr als 6k+ Komponenten enthält. Wenn ich mich für den Interface-Ansatz entscheide, wird mir das Entwicklungsteam gute Fragen stellen, bevor es die Änderungen vornimmt:

  • Welche Schnittstelle müssen wir wo verwenden, da dieselbe Entität in mehreren Komponenten mit unterschiedlichen Eigenschaften verwendet wird. Müssen wir dann die separate Schnittstellenkomponente erstellen? Wenn ja, dann lesen Sie den zweiten Fall.
  • Wenn wir den obigen Ansatz verfolgen, was ist dann der Ansatz, bei dem ich beide Schnittstelleneigenschaften in einer oder mehreren Komponenten verwenden muss. Zur Lösung dieses Problems kann ich eine weitere Schnittstelle erstellen und dieselbe erweitern. Ist es gut, immer mehr Dateien nur für Strongly Type Reactive Form zu erstellen? Was ist mit der Wartbarkeit? Ich habe keine Antwort, außer dass das Angular-Team diese Lösung bereitstellt, also ist sie gut :) (wenn sich Angular-Team für den Schnittstellenansatz entscheidet).
  • Wenn wir einen a+b-Ansatz verfolgen, sind dann in einigen meiner Komponenten nur wenige Eigenschaften erforderlich, nicht alle? Ich habe drei Lösungen für mein Entwicklungsteam.

    • Erstellen Sie eine neue Schnittstelle und kopieren Sie die erforderlichen Eigenschaften in die neu erstellte Schnittstelle. Dies ist der schmutzigste Ansatz in der Softwarewelt. Dies führt zu vielen Problemen, wenn eine einzelne Eigenschaft auf der Serverseite geändert wird, dann ist es schwierig, die Bereiche gleichermaßen zu verfolgen, in wie vielen Schnittstellen der Eigenschaftsname geändert werden muss.

    • Legen Sie die Eigenschaft auf NULL fest. Wenn ich die Eigenschaft nullable definiere, warum muss ich dann dem 'B'-Ansatz folgen?. Auch hier habe ich keine Antwort :( um meinem Entwicklungsteam zu geben.

    • Erstellen Sie keine weitere Schnittstelle, verwenden Sie den Dienstprogrammtyp 'Teilweise' und machen Sie jede Eigenschaft optional. Dadurch verlieren wir den eigentlichen Vorteil der Schnittstelle. Dies ist auch gegen die Praktiken. Wenn ich das befolgen muss, warum ich dem 'A'-Ansatz folgen muss, wieder Keine Antwort :).

    • Wenn ich alle/wenige Eigenschaften nullbar mache, was ist dann mit der Lesbarkeit des Codes und wie kann ich beurteilen, wie viele Eigenschaften für die Übergabe des Werts an den Server erforderlich sind. Dann muss ich das jeweilige Bauteil einchecken und einen Blick darauf werfen. Wichtige Bedenken hinsichtlich der Lesbarkeit des Codes.

Jetzt nur die obigen Fälle in einer größeren Perspektive überdenken und mit TypeScript-Typen mit Interface auf reaktiven Formularen für Strongly Typed vergleichen. Ich glaube, dass jeder gute Ansatz Entwicklungszeit sparen wird, und in diesem Ansatz muss ich leider sagen, dass ich keinen Nutzen gemäß den Software-Design-Prinzipien und -Praktiken sehe.

  1. Laufzeitvalidierung mit Hilfe von Dekoratoren - das ist gut.

Ich stimme Ihrem Kommentar zu " Es ist gut " zu, dem Dekorator-Ansatz, den wir im Interface-Ansatz nicht erreichen können. Ich glaube, dies ist die leistungsstärkste Funktion von TypeScript, weshalb wir sie nicht im Reactive Form Approach verwenden und dem Entwicklungsteam die volle Kontrolle über ihre Objekteigenschaften geben können.

  1. Warum brauchen Sie setValue(), wenn es Setter gibt?

Wo habe ich gesagt, dass ich 'setValue()' brauche? Ich brauche setValue nicht und habe es in dem Beispiel nicht gezeigt, in dem ich die setValue-Methode im klassengesteuerten Ansatz aufrufe. Bitte korrigiert mich, wenn ich falsch liege.

  1. Das Schreiben von TypeScript-Typen ist wie das Schreiben statischer Tests. Jemand kann Anwendungen ohne Tests erstellen, weil er dies für unnötig hält, aber es ist eine schlechte Praxis.

Ich sage nicht, dass die TypeScript-Typen wie das Schreiben statischer Tests sind. Aber ich stimme den Commit-Änderungen in den Basisklassen der reaktiven Form nicht zu, ich glaube, wir können dasselbe erreichen, ohne die Klassendefinition zu berühren. Hier können wir die tatsächliche Leistung der Schnittstelle verwenden, die wir bisher gemäß den Commits nicht verwenden. Ist dies eine gute Vorgehensweise, dass die Logik so lange läuft und wir die generischen Typen hinzufügen, indem wir den Standardwert von ' beliebig'?
Ich denke, dass wir dasselbe erreichen können, ohne die Basisklassen von Reactive Form zu berühren. Ich weiß nicht, warum wir hier nicht die Vorteile von Interface nutzen, anstatt die Basisklassendefinition und auch die Spezifikation zu ändern.

  1. Laufzeitvalidierung mit Hilfe von Dekoratoren - das kann eine gute Idee sein, stimme zu.

Gut zu wissen, dass wir hier beide auf derselben Seite sind :).

  1. Neben setValue() gibt es auch patchValue(), reset(), die auch mit Formularwert arbeiten. Wenn Sie nur setValue() durch einen Setter ersetzen, wird der Code inkonsistent. Wenn wir Setter für jede Formularmodelleigenschaft schreiben müssen, wird dies außerdem viel mehr Code- und Performance-Overhead verursachen. Das Mischen von Formularmodell-Eigenschaftsnamen mit Formularsteuerelement-Eigenschaften ist meiner Meinung nach ebenfalls eine schlechte Idee.

Lassen Sie mich den obigen Punkt in drei Abschnitten beschreiben: Aufrufmethode, Setter-Performance-Overhead und Mischen von Formularmodelleigenschaften.

Aufrufmethode: Wie erwartet, dachte ich beim Schreiben dieses Beitrags, dass mir jemand vorschlagen könnte, die Methode 'patchValue' oder 'reset' zu verwenden. Ich möchte noch einmal sagen, dass im realen Fall das Entwicklungsteam hauptsächlich die Methode "setValue" anstelle von patchValue oder anderen Methoden verwendet (dies ist meine Erfahrung gemäß der Angular Application Code Review und Stackoverflow Posts setValue vs patchValue). Mein Fokus liegt nur darauf, die Methode zum Zuweisen des Werts aufzurufen, egal welche Methode wir aufrufen.

Setter-Leistung : Ich stimme der Aussage von Settern zu, die einen Performance-Overhead verursachen. Wenn dies der Fall ist, würde ich sagen, dass wir zuerst in Angular Project eine Korrektur vornehmen müssen, da Angular Framework zum Binden des reaktiven Formulars die Setter-Methode in der Control Value Accessor-Klasse und so viele andere Direktiven verwendet, und dies erzeugt einen Performance-Overhead, ohne das zu verwenden Klasse Ansatz. Eine weitere Sache, derselbe Ansatz, den wir auch in mehreren Komponenten mit @Input- Dekorator verwenden, müssen wir finden, dass das alternative oder Angular-Team eine andere Lösung (glaube ich) bereitstellen muss, um diese Art von Leistungsproblem zu überwinden. Ich denke also, das ist kein großes Problem. Kommen wir nun zum Leistungsproblem. Vergleichen Sie bitte mit dem bestehenden Ansatz und dem Setter-Methodenansatz (dies ist optional, das Entwicklungsteam kann sich entscheiden, wenn es das gleiche wie ChangeDetectionStrategy in Angular möchte. Bitte beachten Sie das Beispiel auf der rxweb-Dokumentationsseite für die Auswahl Beurteilen Sie, wie viele Funktionen aufrufen, wenn wir den Wert abonnieren, ändert sich dann nach dem Setzen des Werts oder dem direkten Aufrufen der Setter-Methode.Ich glaube, dies ist viel intuitiver in Bezug auf weniger Codeausführung als im Vergleich zu Wertänderungen, geringe Größe des Builds Paket, Codelesbarkeit und so viele andere gute Dinge.

Mischen der Eigenschaften : Also, was ist Ihre Meinung, weisen Sie einen anderen FormControl-Eigenschaftsnamen zu als den vom Server zurückgegebenen Eigenschaftsnamen. Wenn ja, dann würde ich sagen, dass dies ein großes Problem im Code ist, da ich jedes Mal, wenn ich den Eigenschaftsnamen ändern muss, bevor ich ihn auf dem Server poste, Entschuldigung, aber ich würde es nicht vorziehen, die gesamte Anwendung zu verwenden. Wenn ich Ihre gute Meinung für mein Bewerbungsformular berücksichtige, das durchschnittlich 40+ Felder enthält, muss ich jeden Eigenschaftswert manuell einstellen, nur um an den Code in der Komponente zu denken, nur um den Wert und die Prod-Build-Größe zuzuweisen. Ist das eine bessere Meinung als der Klassenansatz?
Kommen wir nun zum Lösungsvorschlag, wir vermischen nicht zwei Dinge in einem. FormControl-Eigenschaften unterscheiden sich und Klasseneigenschaften unterscheiden sich vom jeweiligen Datentyp. Wenn Sie den Eigenschaftsnamen ändern möchten, da der FormControl-Eigenschaftsname sich von der Data-Eigenschaft unterscheidet, können Sie dies in der Dokumentation zum reaktiven Formularpaket von rxweb nachlesen. Es gibt also kein Problem, da Ihr schlechtes Gefühl (das Mischen des Eigenschaftsnamens mit Formularsteuerelementnamen) im vorgeschlagenen Ansatz eine Lösung bietet.

Ich hoffe, ich habe alle Ihre Bedenken beantwortet. Wenn Sie weitere Bedenken diesbezüglich haben, können Sie diese gerne mitteilen :).

Wie ich in meinem vorherigen Kommentar sagte, besteht keine Notwendigkeit, die Basisklassen von Reactive Form zu ändern, da wir die gleichen Dinge erreichen können, indem wir die Leistungsfähigkeit der Prinzipien der Schnittstellentrennungspraktiken nutzen. Hier ist die End-to-End Strongly-Typed Reactive Form- Lösung mit dem Paket von @rxweb/types . Dies funktioniert sowohl mit dem Interface als auch mit dem Klassenansatz :).

Wie sieht der Code nach der Implementierung aus?

Stackblitz: Öffnen
Github : Beispiel für ein schnittstellengesteuertes stark typisiertes reaktives Formular

Jemand hat einen Vorschlag, den Sie gerne teilen.

Also, Version 10 von Angular jetzt verfügbar , dies ist eine Hauptversion und anscheinend reaktive Formulare werden nicht bis mindestens Version 11 von Angular stark typisiert. Wir müssen also mindestens bis zum Herbst warten, um diese Funktion zu implementieren.

Ich habe eine Frage (oder einen Vorschlag?) bezüglich der Art und Weise, wie das Formularmodell in den meisten Vorschlägen/PRs aufgebaut ist, die ich hier gesehen habe.

Wenn Sie sich die meisten Bibliotheken und PRs ansehen, die versuchen, den Typ Reactive Forms sicher zu machen, können Sie feststellen, dass sie ein Modell erstellen, das wie folgt aussieht:

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

dies wird dann in etwa so "übersetzt":

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

Also vereinfacht gesagt: "Wenn es ein Objekt ist, dann erstelle eine FormGroup dafür. Wenn es ein Array ist, dann erstelle ein FormArray. Und wenn es ein primitiver Wert ist, dann erstelle ein FormControl."

Es gibt jedoch ein Problem: Sie können keine Objekte mehr in FormControls verwenden.

Die Lösungen, die ich bisher gesehen habe: Einige Bibliotheken unterstützen dies einfach nicht. Und einige Bibliotheken verwenden eine Art "Hack", um einen Hinweis darauf zu erstellen, dass Sie tatsächlich ein FormControl anstelle einer FormGroup verwenden möchten.

Meine Frage/Vorschlag: Was spricht dagegen, das Formularmodell explizit wie folgt zu definieren?

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'),
  })
})

Dies hat den großen Vorteil, dass Sie nun Objekte in FormControls einfügen können. Und es erfordert keine Art von "Hack", um dies zu tun :)

Ich habe dafür eine Codesandbox erstellt, damit ihr es vielleicht selbst ausprobieren könnt: https://codesandbox.io/s/falling-grass-k4u50?file=/src/app/app.component.ts

@MBbuchalik , ja, dies ist die erste offensichtliche Entscheidung, die Ihnen in den Sinn kommt, wenn Sie mit der Arbeit an "stark typisierten Formularen" beginnen. Ich habe auch damit angefangen, aber es hat einen erheblichen Nachteil - die Notwendigkeit, zwei Modelle zu erstellen: eines für Formularsteuerelemente, das andere - für Formularwerte.

Auf der anderen Seite, soweit ich das verstanden habe, ermöglicht uns diese Lösung, "stark typisierte Formulare" zu implementieren, ohne Änderungen zu verursachen, und wir müssen nicht auf die Veröffentlichung der nächsten Hauptversion von Angular warten. Hier ist es notwendig, in der Praxis mit einer solchen Lösung zu arbeiten, um zu beurteilen, ob sie schwerwiegendere Mängel aufweist als die Notwendigkeit, zwei Modelle zu erstellen.

@MBuchalik Ich habe die gleichen Meinungen mit Ihnen geteilt und die gleiche Frage an die PR gestellt und einer der eckigen Mitwirkenden ( @KostyaTretyak ) hat geantwortet.

Schauen Sie sich die Diskussion in der PR an:
https://github.com/angular/angular/pull/37389#discussion_r438543624

TLDR;

Hier sind ein paar Probleme:
Wenn wir Ihrem Ansatz folgen, müssen wir zwei verschiedene Modelle erstellen - für Formularsteuerelemente und für Formularwerte.
Das Modell für Formularsteuerelemente ist schwieriger zu lesen.

Und diese Idee habe ich vor einem halben Jahr fullTemplateTypeCheck aktiviert)

Ich habe im vorherigen Kommentar in diesem Thread geteilt, "warum ich diesen Weg gehe":
https://github.com/angular/angular/issues/13721#issuecomment -643214540

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

Danke für eure Einblicke @KostyaTretyak und @gaplo917! 👍

Wenn ich es richtig verstanden habe, können wir es wie folgt zusammenfassen.

Wenn wir nur ein einziges Modell verwenden möchten, könnte eine Lösung wie die von

Wenn wir Objekte in FormControls verwenden wollen, dann führt wahrscheinlich (!) kein Weg an einem Ansatz vorbei, wie ich ihn (oder @gaplo917) dargestellt habe. Der Nachteil ist, dass Sie grundsätzlich 2 Modelle benötigen. Oder verwenden Sie zumindest einige Hilfstypen, um das Formularwertmodell zu "extrahieren".

Jetzt müssen wir uns also nur noch überlegen, ob Objekte in FormControls möglich sein sollen oder nicht. Damit wäre lediglich die Frage beantwortet, welcher der beiden Ansätze der zu wählende ist. Oder übersehe ich etwas?

Danke für eure Einblicke @KostyaTretyak und @gaplo917! 👍

Wenn ich es richtig verstanden habe, können wir es wie folgt zusammenfassen.

Wenn wir nur ein einziges Modell verwenden möchten, könnte eine Lösung wie die von

Wenn wir Objekte in FormControls verwenden wollen, dann führt wahrscheinlich (!) kein Weg an einem Ansatz vorbei, wie ich ihn (oder @gaplo917) dargestellt habe. Der Nachteil ist, dass Sie grundsätzlich 2 Modelle benötigen. Oder verwenden Sie zumindest einige Hilfstypen, um das Formularwertmodell zu "extrahieren".

Jetzt müssen wir uns also nur noch überlegen, ob Objekte in FormControls möglich sein sollen oder nicht. Damit wäre lediglich die Frage beantwortet, welcher der beiden Ansätze der zu wählende ist. Oder übersehe ich etwas?

@MBbuchalik Wenn Sie dem Typescript-Compiler vertrauen und sich stark auf die Funktion "Typrückschluss" verlassen, benötigen Sie meiner Meinung nach keine 2 Modelle. Unser internes System hat über 60 Formulare, einige davon sind sehr komplex verschachtelt mit 3 Tiefenebenen FormArray-FormGroup-FormArray und wir brauchen auch kein explizites Modell für den Werttyp.

Es gibt nur 2 Arten von Datenmodellen, mit denen man spielen kann:

  • API-Datenanforderungs-/Antwortmodell
  • FormControl-Modell

99,9 % der Zeit, wir

  1. Erstellen Sie eine Kapselung für jede komplexe Form
  2. Transformieren Sie die Remote-Daten -> Formulardaten
  3. Transformieren Sie die Formulardaten -> Remote-Nutzlast

der folgende Codeausschnitt ist die 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 Dies ist eine eigenwillige Datenflussarchitektur, mit der wir typsicher in Typescript programmieren können

Wenn wir nur ein einziges Modell verwenden möchten, könnte eine Lösung wie die von

Hier ist es noch notwendig abzuschätzen, wie oft wir ein Objekt für FormControl . Ich denke, es kann irgendwo auf 5-30% geschätzt werden. Das heißt, wenn wir Lösungen mit einem Modell verwenden, können wir 70-95% der Fälle abdecken, in denen FormControl . Für den Rest - geben Sie einfach einen Hinweis für TypeScript als zusätzlichen Typ (siehe Control<T> , es ist nicht richtig, es "zweites Modell" zu nennen):

interface FormModel {
  date: Control<Date>;
}

Kann man den Typ Control<T> als Hack bezeichnen? - Ja, es ist wahrscheinlich ein Hack, aber kein grober Hack. Mir sind keine Fälle bekannt, in denen diese Art nicht wie beabsichtigt wirkt oder Nebenwirkungen hat.

Oh, ich habe mich an die Nebenwirkungen von Control<T> wenn wir eine externe Bibliothek für das Formularwertmodell verwenden müssen. In solchen Fällen werden wirklich zwei Modelle benötigt:

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.

Aber in diesem Code ist der Overhead nur hier:

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

Dank @ArielGueta ist nun ein kritisches Problem mit dem Typ Control<T> bekannt geworden. Das heißt, ich werde nicht einmal versuchen, Control<T> in zukünftigen Pull Requests für Angular zu implementieren, wie ich es zuvor geplant hatte.

Dank @ArielGueta ist nun ein kritisches Problem mit dem Typ Control<T> bekannt geworden. Das heißt, ich werde nicht einmal versuchen, Control<T> in zukünftigen Pull Requests für Angular zu implementieren, wie ich es zuvor geplant hatte.

@KostyaTretyak Es ist nicht wahr. Das kritische Problem zeigt nur, dass Ihre "ControlType"-Implementierung falsch ist.

Eine vollständige "Control Type"-Implementierung hat kein Problem.

Live-Demo: https://codesandbox.io/s/lucid-bassi-ceo6t?file=/src/app/demo/forms/type -test.ts

Screenshot 2020-07-01 at 00 35 11

Das heißt, ich werde nicht einmal versuchen, Control zu implementierenin Zukunft Pull Requests für Angular, wie ich es vorher geplant hatte.

OK, das heißt also, dass Ihre PR (und die nachfolgenden) höchstwahrscheinlich nie Objekte in FormControls unterstützen werden?

@MBuchalik , im Moment (Angular v10), wenn wir das folgende Formularmodell haben:

interface FormModel {
  date: Date;
}

und wenn wir in unserer Komponente auf den Wert der Eigenschaft date zugreifen möchten, müssen wir Folgendes tun:

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

Meine aktuelle Pull-Anfrage bietet einen generischen Wert für den Formularwert, jedoch keinen Typ für die Formularsteuerung:

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

@gaplo917 , @MBbuchalik , ich habe Ihre Lösungen ausprobiert und versucht, meine eigene ähnliche Lösung zu implementieren, aber sie funktionieren nicht alle perfekt. Diese Lösung bietet auch eine Reihe von Typen zum rekursiven Extrahieren von Formularmodellwerten. Overhead- und Breaking Changes sind sehr bedeutsam, siehe PR-Entwurf .

Ich bezweifle stark, dass diese Lösungen derzeit zur Implementierung in Angular vorgeschlagen werden sollten. Das heißt, wir müssen Generika vorerst nur für Formularwerte verwenden, nicht für Formularsteuerelemente.

aber sie funktionieren nicht alle perfekt

Ich habe nur ein paar Stunden mit meiner Illustration verbracht, also habe ich nicht erwartet, dass sie perfekt ist ;) Können Sie Beispiele für Dinge geben, die nicht gut funktionieren? (Besonders bei Dingen, die aus Ihrer Sicht nicht einfach zu beheben sind?)

Übrigens ein Vorschlag zur Abwärtskompatibilität: Aus meiner Sicht ist es relativ schwierig, die Implementierung komplett abwärtskompatibel zu gestalten. Aus diesem Grund könnten wir vielleicht Folgendes tun: Wir ändern die Klassen FormControl, FormGroup und FormArray überhaupt nicht. Stattdessen erstellen wir neue, die von ihnen erben (vielleicht nennen wir sie StrictFormControl<T> und StrictFormGroup<T> oder wie auch immer Sie möchten). Dies sind dann diejenigen, die wir typsicher machen. Der Vorteil: Wir sind 100% sicher, dass kein Breaking Change gemacht wird. :)

paar Stunden auf meiner Illustration, also habe ich nicht erwartet, dass sie perfekt ist ;)

Ich habe ein paar Tage mit dieser Lösung gearbeitet und sehe, wie schwierig es sein wird, mit Formularen zu arbeiten.

  1. Erstens ein erheblicher Overhead und die Notwendigkeit, zwei Modelle zu haben.
  2. Zweitens ist diese Lösung in Bezug auf die Zuverlässigkeit nicht besser als meine Lösung mit dem Typ Control<T> , da auf die gleiche Weise der Wert des Formularmodells rekursiv extrahiert werden muss.
  3. Arbeiten Sie mit verschachtelten Formularsteuerelementen. Wenn wir das folgende Formularmodell haben:
interface FormModel {
  one: FormGroup<{two: FormControl<string>}>;
}

Und wenn wir formGroup.controls.one.value , liefert TypeScript einen Hinweis mit bedingtem Typ, nicht mit {two: string} Typ (wie es sein sollte). Also Wert schwer von IDE zu lesen.

Und wenn wir formGroup.controls.one.value erhalten, liefert TypeScript einen Hinweis mit bedingtem Typ, nicht mit {two:string}-Typ (wie es sein sollte). Also Wert schwer von IDE zu lesen.

Okay, nur um sicher zu gehen, dass ich alles richtig verstanden habe. Wenn Sie meine Implementierung verwenden und Folgendes schreiben:

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

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

(habe es etwas ausführlicher gemacht ;) )

Wenn ich jetzt nach myForm.controls.one.value suche, sieht das so aus:

grafik

Sie sagen also, dass in diesem Beispiel "zwei" nicht optional sein sollte? Ich denke, dies ist nicht der richtige Weg, um den Formularwert einzugeben. Der Formularwert enthält nur nicht deaktivierte Felder. Aus meiner Sicht sollte es also ein rekursives Partial sein. Sie können zur Kompilierzeit nicht wissen, welche Felder deaktiviert werden und welche nicht.

Sie sagen also, dass in diesem Beispiel "zwei" nicht optional sein sollte?

Was? Nein.

Mein Test deiner Lösung:

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

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

Was ich in Codesandbox sehe, nachdem ich die Maus auf value bewegt habe :

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

Hier bezieht sich PartialFormGroupValue auf den bedingten Typ PartialFormValue .

Ah, ok, ich glaube, ich habe es verstanden. Sie meinen also, dass die Schrift schwer zu lesen ist, oder? Ich dachte ursprünglich, du redest von einem Fehler oder so.

Nun, die meisten IDEs präsentieren immer noch nur die Vorschläge für die verfügbaren Eigenschaften, wenn Sie mit der Eingabe fortfahren. Daher sehe ich hier keine wirklich großen Probleme. (Natürlich wäre es besser zu lesen, wenn es nur {two?: string} sagen würde. Aber ich denke nicht, dass das super wichtig ist. Das ist zumindest meine Meinung.)

Wenn Sie Ihr Control<T> implementieren würden, wie würden Sie es dann aus der Eingabe des Formularwerts entfernen, ohne etw zu tun, wie ich es getan habe? Und wie würden Sie den Formularwert zu einem rekursiven Teil machen, ohne einen Hilfstyp zu verwenden?

Wenn Sie Ihr Control implementiert haben, wie würden Sie es dann aus der Eingabe des Formularwerts entfernen, ohne etw zu tun, wie ich es getan habe? Und wie würden Sie den Formularwert zu einem rekursiven Teil machen, ohne einen Hilfstyp zu verwenden?

In diesem Fall ist meine Lösung nicht besser:

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

Ich habe dieses Beispiel gegeben, weil Sie danach gefragt haben:

Können Sie Beispiele für Dinge nennen, die nicht gut funktionieren? (Besonders bei Dingen, die aus Ihrer Sicht nicht einfach zu beheben sind?)

Übrigens habe ich ein kritisches Problem mit Control<T> behoben.

Um die HTML-Bindungsprobleme mit Angular 10 und [formControl] zu beheben, bin ich wie folgt vorgegangen:

Wie in einer anderen Ausgabe (https://github.com/angular/angular/issues/36405#issuecomment-655110082) erwähnt, erstelle ich für meine Formulare im Allgemeinen Klassen, die FormGroup , um die Wiederverwendbarkeit und das Testen zu erleichtern . Mit dieser Struktur konnte ich das Problem vorerst lösen, indem ich meinen Code wie folgt aktualisiert habe:

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

Dazu:

// 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());
}

An diesem Punkt zeigt form.controls seinen Typ korrekt als { id: FormControl, name: FormControl } , bindet korrekt im HTML-Code und würde korrekt nach unten aggregieren, wenn das Formular mit verschachtelten Formulargruppen oder Arrays komplizierter wäre.

Die Verwendung der Funktion formDefinition ist nicht schön, aber es war die sauberste Lösung, die ich finden konnte, um Duplikate zwischen der Formulardefinition und dem Konstruktor zu verhindern.

Ich glaube, Sie könnten FormGroup aktualisieren, um die obige generische Typdefinition zu erhalten, ohne Breaking Changes einzuführen (nun, das trifft möglicherweise nicht für Formulare zu, die dynamisch Steuerelemente hinzufügen/entfernen, denke ich; sie würden nicht in der controls angezeigt

bearbeiten
Es sieht noch einfacher aus, wenn Sie keine Klassen erstellen müssen, die FormGroup erweitern. Sie könnten eine Hilfsfunktion erstellen, die das allgemeine Problem behebt:

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),
});

bearbeiten 2
... oder du könntest es in die FormGroup Klasse selbst backen ( FormBuilder vielleicht?)

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),
});

bearbeiten 3
Ich habe die obigen Beispiele um die Eingabe der value s erweitert und einen Artikel erstellt, um alles zusammenzufassen:

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

Dies ist jetzt auf der Roadmap für die zukünftige Entwicklung markiert: https://angular.io/guide/roadmap#better -developer-ergonomics-with-strict-typing-for-angularforms

@pauldraper Könnten Sie erklären, was sich im Vergleich zur Roadmap von vor ~2 Monaten geändert hat? Die einzige Änderung, die ich sehe, ist der Wortlaut des Titels. Aber es ist immer noch im Abschnitt "Zukunft". Genauso wie vor 2 Monaten.

@MBuchalik vielleicht ist es seit 2 Monaten da.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen