Angular: FormControl.setValidators...warum gibt es keine getValidators?

Erstellt am 14. Dez. 2016  ·  72Kommentare  ·  Quelle: angular/angular

Ich sende ein ... (mit "x" ankreuzen)

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Aktuelles Verhalten
Wir haben ein AbstractControl mit grundlegenden Validatoren wie (erforderlich, maxLength) in einer ReactiveForm über TypeScript FormBuilder. Zu diesen grundlegenden Validatoren haben wir einen benutzerdefinierten Validator hinzugefügt, der die Validierungsstrategie basierend auf einem Dropdown-Wert dynamisch ändert. Derzeit verwenden wir die Methode setValidators() in einer anderen Komponente (AbstractControl wird über @Input() ). Das Hauptproblem besteht darin, dass vorhandene Validatoren überschrieben werden.
Beispiel:

App-Komponente

this.formBuilder.group({
      zip: ['', [Validators.required, Validators.maxLength(10)]] 
    });

App-Vorlage

<zip ...
        [control]="form.controls.zip">
</zip>

ZIP-Komponente

@Input() control: AbstractControl;

this.control.setValidators([
      // redundant to formBuilder
      Validators.required,
      Validators.maxLength(10),
      // custom validation based on a dropdown value (via. valueChange Detection)
      validateZipFn(countryCode)]
    );

Erwartetes Verhalten
Um flexibel zu bleiben, wollen wir nicht alle Validatoren überschreiben. Der folgende Code würde das Verhalten mit einer getValidators() -Methode veranschaulichen.

App-Komponente

this.formBuilder.group({
      zip: ['', [Validators.required, Validators.maxLength(10)]]
    });

App-Vorlage

<zip ...
        [control]="form.controls.zip">
</zip>

ZIP-Komponente

@Input() control: AbstractControl;

let listOfAllValidationRules = this.control.getValidators().push(validateZipFn(countryCode)]);
this.control.setValidators(listOfAllValidationRules);

Was ist die Motivation / der Anwendungsfall für die Änderung des Verhaltens?
Flexibler sein mit dynamischer Validierung.

  • Winkelversion: ~2.1.2

  • Browser: alle

  • Sprache: TypeScript / ES6

forms feature medium

Hilfreichster Kommentar

Ich stimme zu, eine schreibgeschützte Liste von Validatoren zu haben, die durch eine Methode für die aktuelle Kontrolle abgerufen werden könnten, wäre sehr hilfreich.

Alle 72 Kommentare

@Icepick es gibt nichts zurückzugeben. Validatoren werden als einzelnes Objekt gespeichert, nicht als Array.

@DzmitryShylovich Bedeutet das, dass dies nicht implementiert werden kann? Persönlich benötige ich nur eine schreibgeschützte Kopie der Validatoren mit den angehängten Metadaten.

Ich baue eine Direktive, in der ein Objekt mit Validierungsnachrichten übergeben wird. Dies ist einfach ein Objekt, bei dem der Name der Validierungsregel der Schlüssel ist und der Wert die anzuzeigende Nachricht ist. Ich möchte dieses Objekt und die gesetzten Validatoren des NgControl vergleichen, damit der Benutzer gewarnt wird, wenn eine bestimmte Meldung fehlt.

Das würde ich auch aus ähnlichen Gründen wie @Bidthedog gerne sehen. Wenn sich beispielsweise ein Validator.requried auf einem Steuerelement befindet, möchten wir dies programmatisch auf dem Formular angeben. Read-only ist alles, was wir brauchen. Derzeit ist das Beste, was ich tun kann, ein Array in der Komponente zu definieren und dieses an die Definition des Formularsteuerelements zu übergeben. Es wird ziemlich schnell hässlich!

Ist dies auf der Roadmap und wenn ja, gibt es eine ETA für diese Funktionalität?

Exakt. Ich habe jetzt im Grunde drei große böse doppelte Definitionen in meinem Formulardienst. Die folgenden Codeausschnitte sind ein winziger Auszug aus einem ziemlich großen, 6-seitigen Formular, das ich zusammenstelle. Dies ist für Seite eins (Details) und den Anfang von Seite 2 (Medizin):

Erstellen / definieren:

  private createForm() {
    this.form = this.fb.group({
      details: this.fb.group({
        NHSNumber: [null, [Validators.required, Validators.pattern(/^\d{3}-\d{3}-\d{4}$/)]],
        titleId: [null, [Validators.required]],
        firstName: [null, [Validators.required, Validators.maxLength(10)]],
        middleNames: null,
        lastName: [null, [Validators.required]],
        DOB: [null, [Validators.required]],
        genderId: [null, [Validators.required]],
        regionId: [null, [Validators.required]],
        maritalStatusId: [null, [Validators.required]],
        occupationTypeId: [null],
        occupationId: null,
        telephoneNo: null,
        mobileNo: null,
        houseNumber: null,
        streetName: null,
        town: null,
        city: null,
        county: null,
        postcode: null,
        GPId: null,
        nextOfKinId: null,
        livesWith: null
        //vehicleLicenseTypeId?: number[]
      }),
      medical: this.fb.group({
        gpId: [null, [Validators.required]]
      })
    });
  }

Bevölkern:

  private populateForm() {
    let m = this.model;
    this.form.setValue({
      details: {
        NHSNumber: this.getTextValue(m.NHSNumber),
        titleId: this.getLookupValue(m.titleId),
        firstName: this.getTextValue(m.firstName),
        middleNames: this.getTextValue(m.middleNames),
        lastName: this.getTextValue(m.lastName),
        DOB: this.getDateValue(m.DOB),
        genderId: this.getLookupValue(m.genderId),
        regionId: this.getLookupValue(m.regionId),
        maritalStatusId: this.getLookupValue(m.maritalStatusId),
        occupationTypeId: this.getLookupValue(m.occupationTypeId),
        occupationId: this.getLookupValue(m.occupationId),
        telephoneNo: this.getTextValue(m.telephoneNo),
        mobileNo: this.getTextValue(m.mobileNo),
        houseNumber: this.getTextValue(m.houseNumber),
        streetName: this.getTextValue(m.streetName),
        town: this.getTextValue(m.town),
        city: this.getTextValue(m.city),
        county: this.getTextValue(m.county),
        postcode: this.getTextValue(m.postcode),
        GPId: this.getLookupValue(m.GPId),
        nextOfKinId: this.getLookupValue(m.nextOfKinId),
        livesWith: this.getTextValue(m.livesWith)
      },
      medical: {
        gpId: this.getLookupValue(m.GPId) || 1
      }
    });

Validierungsregeln. Nicht vollständig, aber Sie können sehen, wie schnell es chaotisch wird

  private formValidationMessages = {
    details: {
      NHSNumber: {
        required: '\'NHS Number\' is required',
        pattern: '\'NHS Number\' must be in the format \'NNN-NNN-NNNN\' where N is a digit'
      },
      titleId: {
        required: '\'Title\' is required'
      },
      firstName: {
        required: '\'First Name\' is required'
      }
    },
    medical: {
    }
  };

Ich habe damit begonnen, alle Formularregeln in einem Objektsatz zu definieren, den ich durch iterieren / in die oben aufgeführten Funktionen projizieren möchte (obwohl ich noch nicht so weit bin):

  private formValidationMessages = {
    details: {
      NHSNumber: {
        required: '\'NHS Number\' is required',
        pattern: '\'NHS Number\' must be in the format \'NNN-NNN-NNNN\' where N is a digit'
      },
      titleId: {
        required: '\'Title\' is required'
      },
      firstName: {
        required: '\'First Name\' is required'
      }
    },
    medical: {
    }
  };

Darüber hinaus habe ich natürlich die Datenmodelldefinition und dann alle Schichten im Backend; muss es etwas geben, das wir tun können, um Code-Bloat zu reduzieren?

Ich wurde auch gerade davon getroffen, dass ich die konfigurierten Validatoren für ein FormControl nicht abrufen konnte.

Ich habe es umgangen, indem ich einen Hilfsdienst hinzugefügt habe, der das Konfigurationsobjekt umschließt, bevor es an den FormBuilder übergeben wird, und eine Zuordnung zwischen Steuerelementnamen und ihren angegebenen Validatoren verfolgt.

Es wäre natürlich toll, diesen zusätzlichen Service zu beseitigen 😄

Nun, seit ich hier gepostet habe, habe ich meine gesamte Formularkonfiguration in einem großen Objekt konsolidiert und ein paar Hilfsmethoden geschrieben, die die Zuordnung zwischen Formular und Modell basierend auf der Konfiguration vornehmen. Es scheint alles viel zu kompliziert zu sein, aber es könnte nur die Lernkurve sein, die ich hatte.

Hallo,
Ich frage mich nur, ob es ein Wort zu dieser Funktion gibt. Es scheint mir, dass die Möglichkeit, bestimmte Elemente als erforderlich visuell zu markieren, ein grundlegendes Merkmal ist, das von jeder modernen Formular-Benutzeroberfläche erwartet wird. Da es jedoch keine Möglichkeit gibt, die Information abzurufen, ob ein Feld erforderlich ist oder nicht, ist es unmöglich, dieses Feature mit reaktiven Formularen zu implementieren.

Es ist nicht unmöglich, es ist nur eine Menge Arbeit.

@Bidthegod meinst du, es wäre viel Arbeit, diese Funktion in Winkel zu implementieren, oder es wäre viel Arbeit, die Validatoren in einer Winkelkomponente abzurufen.

Wenn es möglich ist, die Validatoren in einer Komponente abzurufen, ist jeder Beitrag zur Vorgehensweise willkommen!

Oder wenn es für bestimmte Komponententypen (Eingabe, Auswahl) einfacher ist, kann es auch nützlich sein.

@Toub Am Ende habe ich eine "config" -Objektstruktur erstellt, die jedes einzelne Formularfeld und alle seine Einstellungen (Anzeige, Validatoren und Regeln usw.) enthielt. Ich habe dann ein paar Hilfsmethoden geschrieben, um die Konfiguration abzufragen, die die FormGroup aufbaut. Jedes Formular hat einen Formulardienst und erbt von einer Basiskomponente.

Ich würde nicht sagen, dass es eine einfache Lösung ist, und es sind Tonnen von Code, aber es war ziemlich elegant und einfach, ein ziemlich komplexes Formular zu erstellen. Ich habe einen Teil des Codes in eine Zusammenfassung geworfen, damit Sie ihn sich ansehen können, aber ich habe im Moment keine Zeit, ihn richtig zu modularisieren. Es fehlen einige Dateien - einschließlich aller benutzerdefinierten Formulareingabekomponenten, die ich geschrieben habe.

https://gist.github.com/Bidthedog/1dc7d10cda1759061c09f44f7b48cbf3

@Bidthedog es erfüllt nicht meine Bedürfnisse, aber danke für deine Antwort, da das Wesentliche für andere nützlich sein kann.

Hey Leute, ich habe einen einfachen Helfer erstellt, um die Validatoren zurückzugeben (nur für die erforderlichen, kann aber für andere erweitert werden):

getValidators(_f) {
  return Object.keys(_f).reduce((a, b) => {
    const v = _f[b][1];
    if (v && (v === Validators.required || v.indexOf(Validators.required) > -1)) {
      if (!a[b]) { a[b] = {}; }
      a[b]['required'] = true;
    }
    return a;
  }, {});
}

const _f = {
  id: [],
  name: [null, Validators.required],
  phone: [],
  email: [null, [Validators.required, Validators.email]]
};
this.frmMain = this._fb.group(_f);
console.log(this.getValidators(_f));    // {name: {"required": true}, email: {"required": true}}

Passt zu meinen Bedürfnissen 👍

Ich stimme zu, eine schreibgeschützte Liste von Validatoren zu haben, die durch eine Methode für die aktuelle Kontrolle abgerufen werden könnten, wäre sehr hilfreich.

+1 für diese Funktion

+1 für schreibgeschützte Liste

+1

Ich brauche diese Funktionalität, um Validatoren dynamisch hinzuzufügen, ohne die bereits vorhandenen zu verlieren. Etwas wie:
Object.keys(this.myForm.controls).forEach(key => { if (map.get(key)) { this.myForm.get(key).setValidators(this.myForm.get(key).getValidators().push(Validators.required)) } });
Vollständig erklärt in: https://stackoverflow.com/questions/46852063/how-to-dynamically-add-validators-to-the-forms-in-angular-2-4-using-formbuilde .
Das muss etwas Gemeinsames sein

Es ist eigentlich nicht allzu schwer, Validatoren mit composeValidators (https://github.com/angular/angular/blob/master/packages/forms/src/directives/shared.ts#L139) dynamisch hinzuzufügen. Ich denke, das Entfernen von Validatoren ist das größere Problem. Sie können dies mit einem booleschen Wert tun, aber dann ist Ihr Validator keine reine Funktion mehr.

Diese Funktion sollte für FormGroups und FormControls funktionieren, um erforderliche Validatoren zu bestimmen

      export const hasRequiredField = (abstractControl: AbstractControl): boolean => {
        if (abstractControl.validator) {
            const validator = abstractControl.validator({}as AbstractControl);
            if (validator && validator.required) {
                return true;
            }
        }
        if (abstractControl['controls']) {
            for (const controlName in abstractControl['controls']) {
                if (abstractControl['controls'][controlName]) {
                    if (hasRequiredField(abstractControl['controls'][controlName])) {
                        return true;
                    }
                }
            }
        }
        return false;
    };

@mtinner Das war ein wirklich toller Fund. Das bringt mir den ersten Satz. Ich denke, es wäre immer noch sinnvoll, die API zu erweitern, um die Rückgabe eines Observable der Validatoren zu ermöglichen. Auf diese Weise wäre es sehr einfach, eine bedingt erforderliche Kontrolle zu finden. Ich bin mir nicht sicher, ob die hier vorgeschlagene Problemumgehung angewendet werden kann, um diese Instanzen abzufangen.

In einer sehr schönen Präsentation von Angular Connect 2017 endet @kara mit den Worten: "Steuerelemente für benutzerdefinierte Formulare sind großartig und nicht beängstigend". Gut, aber dieses wichtige Feature für Custom Controls fehlt

+1

Keine Zeit für eine PR, aber hier ist, was ich mir ausgedacht habe:

import {FormControl} from '@angular/forms';
import {AsyncValidatorFn, ValidatorFn} from '@angular/forms/src/directives/validators';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {AbstractControlOptions} from '@angular/forms/src/model';

export class DescendingFormControl extends FormControl {

    private asyncValidationChangesSubject = new Subject<AsyncValidatorFn | AsyncValidatorFn[]>();
    private validationChangesSubject = new Subject<ValidatorFn | ValidatorFn[] | null>();

    public readonly validationChanges: Observable<ValidatorFn | ValidatorFn[] | null>;
    public readonly asyncValidationChanges: Observable<AsyncValidatorFn | AsyncValidatorFn[]>;

    constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) {
        super(formState, validatorOrOpts, asyncValidator);
        this.validationChanges = this.validationChangesSubject.asObservable();
        this.asyncValidationChanges = this.asyncValidationChangesSubject.asObservable();
    }

    public setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void {
        super.setValidators(newValidator);
        this.validationChangesSubject.next(newValidator);
    }

    public setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void {
        super.setAsyncValidators(newValidator);
        this.asyncValidationChangesSubject.next(newValidator);
    }
}

Wenn jemand bereit ist, den ganzen PR-Job zu erledigen, bevor ich etwas Freizeit bekomme, machen Sie es!

+1

+1

+1

Das Problem: Es ist gefangen

Eine Reihe vorhandener Validatoren existiert, steht uns aber nicht zur Verfügung...
Werfen Sie einen Blick auf die statische Validators-Methode compose , die hinter den Kulissen verwendet wird, wenn Sie ein Array von Validators in Ihrem Formularsteuerelement definieren:

 static compose(validators: (ValidatorFn|null|undefined)[]|null): ValidatorFn|null {
    if (!validators) return null;

    // Here it is
    const presentValidators: ValidatorFn[] = validators.filter(isPresent) as any; 

    if (presentValidators.length == 0) return null;

    return function(control: AbstractControl) {
      return _mergeErrors(_executeValidators(control, presentValidators));
    };
  }

https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts#L342

Wie Sie sehen können, suchen wir nach presentValidators , aber es ist _gefangen_ in einer Closure, also unzugänglich, und der einzige Wert, den Sie von compose erhalten, ist ein Objekt mit allen ausgelösten Fehlern einen gegebenen Wert.

Die einzige Möglichkeit , die Validatoren abzuleiten, besteht darin, die Fehler in Ihrem Formularsteuerelement manuell auszulösen, die auf die Namen der Validatoren verweisen.

Der Hack: Lösen Sie einen Fehler bei einem anderen Steuerelement aus

Sie möchten keinen Fehler in Ihrem gebundenen Formularsteuerelement auslösen. Sie sollten stattdessen ein neues Formularsteuerelement erstellen, das frei von jeglicher Bindung an Ihre Vorlage ist, und dieselben Validator-Funktionen auf ihn anwenden und dann die gewünschten Fehler auslösen, indem Sie einen Wert festlegen:

// Your form control, actually binded to a form in your template

emailControl = new FormControl('', [Validators.required, Validators.email])

// Elsewhere in your code
// You use dummy form controls, on which you apply your original form control validators and various values, to trigger the errors.

const errorsWhenEmpty = new FormControl('', this.emailControl.validator, this.emailControl.asyncValidator).errors
const errorsWhenOneChar = new FormControl('x', this.emailControl.validator, this.emailControl.asyncValidator).errors

// Or you can simply reuse the first dummy control with `setValue()`

Es ist alles andere als ideal, aber es ist der am wenigsten schmutzige Weg, den ich gefunden habe, bis wir die Validatoren selbst in die Hände bekommen können.

Ich werde vorschlagen, nur die FormControl-Klasse zu erweitern und in den Feldern der untergeordneten Klasse eine Kopie der an den Konstruktor übergebenen Validator-Funktionen zu speichern, etwa so:

export class ExtFormControl extends FormControl {
  private _syncValidators: ValidatorFn | ValidatorFn[];

  private _asyncValidators: AsyncValidatorFn | AsyncValidatorFn[];

  constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | null,
              asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null) {
    super(formState, validatorOrOpts, asyncValidators);
    this._syncValidators = validatorOrOpts;
    this._asyncValidators = asyncValidators;
  }


  getSyncValidators(): ValidatorFn[] {
    if (typeof this._syncValidators === 'function') {
      return [this._syncValidators];
    } else {
      return this._syncValidators;
    }
  }

  getAsyncValidators(): AsyncValidatorFn[] {
    if (typeof this._asyncValidators === 'function') {
      return [this._asyncValidators];
    } else {
      return this._asyncValidators;
    }
  }

  setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void {
    super.setValidators(newValidator);
    this._syncValidators = newValidator;
  }

  setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void {
    super.setAsyncValidators(newValidator);
    this._asyncValidators = newValidator;
  }

}

Ich bin für eine Nur-Lese-Liste von Validierern. Es gibt Fälle, in denen man bei der Verwendung von ReactiveForms erwarten würde, Einschränkungen wie maxLength über den FormBuilder-Code und nicht über die Vorlage zu konfigurieren. Aber es ist derzeit notwendig, maxlength in HTML zu platzieren, um einige Verhaltensweisen definieren zu können (wie das Verschieben des Fokus auf das nächste Feld, ein typischer Fall bei Kreditkarteneingaben), oder das automatische Markieren von Etiketten nach Bedarf usw.

Ich muss programmgesteuert Validatoren festlegen, die aktualisiert werden, wenn das Formular ausgefüllt wird. Es wäre wirklich schön, die Validatoren auf einem FormControl lesen zu können. Ich stimme @moniuch zu, dass man mit ReactiveForms erwarten würde, dies tun zu können. Ich gebe nur meiner Hoffnung Ausdruck, dass es umgesetzt wird.

Irgendwelche Updates, Leute?

Hmm, ich sehe diese Verbindung als ein interessantes Thema im Jahr 19851
Wir können Set-Validatoren nicht sehen oder priorisieren. :(

Ich habe es für mich gelöst, indem ich die Eigenschaft "Validator" des Steuerelements überprüft habe. Es gibt das Objekt mit ACTIVE- Validatoren zurück. Wenn Prüfer gesetzt sind und wenn die Bedingung für den Prüfer erfüllt ist.
In meinem Beispiel ist es sogar eine REAKTIVE FORM , die in einer NESTED COMPONENT verwendet wird .

Übergeordnete Komponente

Die Validatoren in ReactiveFormGroup in der PARENT-Komponente

return this.formBuilder.group({
      username: ['', [Validators.required, Validators.min(3), Validators.email]],
     ...
});

Die Namen dienen nur zu Testzwecken

<app-input name="txtfield" 
[parentForm]="loginForm" parentFormControlName="txtfield" 
></app-input>

Verschachtelte Komponente

 <div [formGroup]="parentForm" class="form-control-group">
   ...
        <input #inputField  name="{{parentFormControlName}}" 
            formControlName="{{parentFormControlName}}" 
            matInput 
            placeholder="{{placeholderTxt}}"
        >
  ...
    </div>

Bei verschachtelten Komponenten überprüfe ich nur einmal nach AfterViewIinit, da ich nur den "erforderlichen" Validator benötige.

ngAfterViewInit() {
    // Lookup if this field is required or not
    if (this.parentForm.controls[this.parentFormControlName].validator != null) {
    // MAYBE CHECK FOR required field in returned OBJECT!!
      console.log('Validators of ' + this.parentFormControlName + ' = ', 
           JSON.stringify(
             this.parentForm.controls[this.parentFormControlName]
                      .validator(this.parentForm.controls['username']);)
            );

    } else {
      console.log('No validator set for component ' + this.parentFormControlName); 
    }

Ausgabe in der Browserkonsole bei gestopptem Debugger:

[DEBUG] STATUS of username =  {"required":true}
>this.parentForm.controls[this.parentFormControlName].validator(this.parentForm.controls[this.parentFormControlName])
{required: true}
required: true
__proto__: Object

Andernfalls - also wenn kein Validator gesetzt ist - wird "null" angezeigt!

Andere Situationen

Wenn das Eingabetextfeld einen Standardwert hat

Wenn das Eingabetextfeld einen Standardwert hat, würde dies den erforderlichen Wert NICHT als wahr anzeigen, aber ich möchte das Feld nur hervorheben (= Zeile fett), wenn das Feld erforderlich und nicht ausgefüllt ist.

Anderer Wert als erforderlich

Sie können jederzeit jeden anderen Validator-Status überprüfen, wenn Sie Änderungen beobachten, vielleicht mit "DoCheck" oder "OnChanges", um vielleicht "E-Mail"- oder "Min"-Validatoren zu erhalten .... wenn Sie dies brauchen.

@Angular -Team, es ist wirklich notwendig, die API zu haben, um die aktuelle Validator-Liste zu überprüfen und einen bestimmten Validator zur Laufzeit zu entfernen. Dies löst viele Probleme.

Warum machen "control.errors" und "controls.validators" ziemlich genau dasselbe?
.errors “ sollte alle Fehler anzeigen, bei denen ein Fehlerzustand erreicht ist.
" .validators " sollte alle registrierten Validatoren für diese Kontrolle zurückgeben.
So einfach ist das?

Diese Funktion würde ich auch sehr gerne sehen. Aber! Eines der großen Probleme, die ich sehe, ist, dass Validatoren (normalerweise) reine Funktionen sind – was bedeutet, dass sie sogar tief verschachtelt sein können.

In unseren Projekten haben wir viele domänenbezogene Validatoren, die dann aus anderen Validatoren konstruiert werden (und so weiter). Unser IBAN-Validator zum Beispiel verwendet einen exactLength , einen pattern und einen IBAN-Prüfziffern-Validator unter der Haube. Das Befolgen dieses Musters führt zu spezifischeren Validierungsfehlern und ermöglicht es uns, Fehlermeldungen zu verwenden, die das Problem detaillierter beschreiben (auch bekannt als besser für die Benutzerfreundlichkeit).

Wenn Sie also eine neue Methode zum Abrufen aller Validatoren in Betracht ziehen, stellen Sie sich die Frage: Werden nur die „Top-Validatoren“ zurückgegeben? Außerdem, wie heißen diese Validierer? Insgesamt scheint mir, dass es bei der derzeitigen Umsetzung der Validierung keine einfache Lösung gibt ...

TL; DR: Es ist unmöglich, ohne Tonnen von Dingen im Angular Forms-Modul zu ändern und Ihren vorhandenen Code zu beschädigen.

Also Leute, der relevante Quellcode ist dieser:
Pakete/Formulare/src/model.ts
Pakete/Formulare/src/validators.ts
und vielleicht packages/forms/src/directives/validators.ts

Folgende Probleme habe ich festgestellt:

  • Zunächst einmal ist der Validator nur ein dummes Funktionsobjekt ohne Möglichkeit, es zu identifizieren. Selbst wenn Sie eine Reihe von Funktionen erhalten könnten, was würden Sie dann tun? Wie können Sie einen Regex-Validator löschen, wenn ein Regex-Validator nur eine Funktion ist? Wir brauchen eine Möglichkeit, um zu erkennen, dass dies ein Regex-Validator ist, und dass dieser ein erforderlicher Validator ist, wenn wir sie einzeln hinzufügen oder entfernen möchten. Sie können also die Funktion addValidator implementieren, aber nicht die Funktion removeValidator .
  • Zweitens, wenn Sie setValidators , wird die Compose-Funktion schließlich aufgerufen . Es fasst alle Validator-Funktionen in einer einzigen Funktion zusammen und ruft sie tatsächlich auf, indem es die interne _executeValidators-Funktion aufruft . Am Ende haben wir also nicht das ursprüngliche Array von Validatoren, wir haben nur einen riesigen Mega-Validator mit der Liste aller zusammengeführten Fehler.

Daraus folgerte ich, dass es unmöglich ist, getValidators oder removeValidator zu implementieren, ohne einige bahnbrechende Änderungen in der Vorstellung von Validatoren einzuführen. Es scheint möglich, addValidators zu implementieren, aber wer braucht es, wenn ich nur weitere Validatoren hinzufügen und die vorhandenen nicht entfernen kann?

Was sollten Sie also tun, wenn Sie eine bedingte, dynamische Validierung in Ihrer App implementieren möchten, um beispielsweise dieses E-Mail-Feld erforderlich zu machen, wenn dieses Kontrollkästchen aktiviert ist, ohne alle vorhandenen E-Mail-Validatoren zu verlieren? Ich habe zwei Ansätze gefunden:

  • Sie können die Standardvalidatoren für Ihre Felder irgendwo in einer Variablen speichern. Wenn es an der Zeit ist, E-Mail erforderlich zu machen, verwenden Sie die Funktion setValidator und übergeben ihr Validator.required + alle Standardvalidatoren.
  • Anstatt den für die Formularsteuerung erforderlichen Validator zu verwenden, können Sie einen Formulargruppen- Validator erstellen, der sowohl das Kontrollkästchen als auch die Änderungen in Ihrem Feld überwacht. Wenn das Kontrollkästchen auf „true“ gesetzt ist, verhält es sich so, als wäre ein E-Mail-Feld erforderlich.

@ganqqwerty … Richtig. Das ist der Grund, warum addValidator(...) oder removeValidator(...) nicht existieren. Es gibt keine Identifikation. Der Validator selbst weiß nicht, auf welchem ​​Steuerelement ausgeführt wird.

@ganqqwerty Ich sage nicht, dass dies einfach zu implementieren ist, aber ich denke nicht, dass Ihre Argumentation hier gültig ist.

Wie können Sie einen Regex-Validator löschen?

Durch Übergabe einer Referenz an den Validator. Auf die gleiche Weise entfernen Sie Ereignis-Listener.

Am Ende haben wir also nicht das ursprüngliche Array von Validatoren

Dies ist nur eine interne Designentscheidung und wird derzeit nirgendwo in der öffentlichen API angezeigt. Die Entscheidung, das ursprüngliche Array beizubehalten, anstatt die Funktionen immer wieder neu zu erstellen, wäre tatsächlich ein möglicher Leistungsvorteil, da Sie keine zusätzliche Funktion hätten, die Ihr Array von Validatoren sowieso in einem Abschluss halten müsste.

um dieses E-Mail-Feld erforderlich zu machen, wenn dieses Kontrollkästchen aktiviert ist, ohne alle vorhandenen E-Mail-Validierer zu verlieren: [...] Anstatt den für die Formularsteuerung erforderlichen Validator zu verwenden, können Sie einen Formulargruppen-Validator erstellen, der sowohl das Kontrollkästchen als auch die Änderungen in Ihrem Feld überwacht .

Ist das nicht das beabsichtigte Verhalten? Ihr Formularsteuerelement mit dem E-Mail-Feld ist allein gültig, wenn es eine gültige E-Mail-Adresse hat. Ihre _Gruppe_ ist gültig, wenn die E-Mail vorhanden ist, falls der Wert des Kontrollkästchens true ist. Ich sehe dies nicht als Problemumgehung für Validatoren, sondern als deren ordnungsgemäße Verwendung auf der richtigen Ebene des Formulars.

Danke, dass du mir das erklärt hast! Bedeutet das, dass eine solche Funktion zur Implementierung geplant werden kann?

Durch Übergabe einer Referenz an den Validator.

Wenn wir also den Verweis auf einen Validator beibehalten, ist es möglich, ihn zu entfernen. Wenn wir nur eine anonyme Funktion als Validator bereitstellen, können wir diese nicht entfernen. Hmmm, tatsächlich sieht es aus wie removeEventListener

Ohne diese Funktion ist es unmöglich, einen bestimmten Validator von einem bestimmten Steuerelement zu entfernen .

Das ist, was ich gerade brauche und ich kann das nicht lösen. Es ist verrückt, wie manchmal super grundlegende Sachen mit eckig einfach nicht möglich sind.

this.myAbstractFormControl.removeValidatorAndKeepTheRestAsIs(Validators.required)

@andreElrico Was ist Ihr Anwendungsfall zum Entfernen eines Validators? Je mehr ich darüber nachdenke, desto weniger sehe ich den Sinn darin, Validatoren auf einem Formular ständig zu ändern. Wenn Sie Validatoren basierend auf einem anderen Feld im Formular dynamisch ändern müssen, dann ist Ihre Platzierung des Validators falsch: Was Sie wollen, ist ein Formulargruppen-Validator, kein Formularkontroll-Validator.

Ich versuche, die gleiche FormGroup zu verwenden, die zB über Input dynamisch in zwei Szenarien bereitgestellt wird. Einen, bei dem ich für jedes Feld erforderlich sein KANN, und den zweiten Fall, bei dem ich das erforderliche IF-Präsent entferne und einen atLeastOneField-Validator auf die formGroup anwende.

Hier ist mein Stackblitz , an dem ich arbeite.

Ich verstehe nicht, warum einige den Designfehler schützen, den das Winkelteam in Bezug auf die bestehende Validierungshandhabung produziert hat.
Angular Form-Validation sollte CRUD sein.

@andreElrico Wie gesagt, wenn Ihr Formular zwei Eingaben hat, die voneinander abhängige Validierungen haben, dann sollte sich Ihr Validator in der Formulargruppe befinden, nicht in der Formularsteuerung.

Wenn Sie beispielsweise zwei Kennwortfelder haben, sollten Sie den Validator des zweiten Steuerelements nicht jedes Mal ändern, wenn sich das erste Steuerelement ändert. Stattdessen sollte der Validator auf eine Gruppe gehen, die zwei Passwortfelder enthält. Jedes Passwortfeld hat eine Validierung, dass das Passwort mindestens 8 Zeichen lang sein muss, aber die Gruppe hat den Validator, dass sie gleich sein müssen. Jedes Passwortfeld für sich genommen ist sich der Existenz des anderen nicht bewusst. Die Gruppe hält sie zusammen.

Wenn Sie so etwas haben

( ) a
( ) b
( ) other [ _____ ]

Wenn der Benutzer "andere" auswählt, ist das Eingabefeld daneben erforderlich, dann sollte der Validator "erforderlich" nicht im Eingabefeld umgeschaltet werden. Stattdessen muss die gesamte Formulargruppe validiert werden: Das Optionsfeld muss entweder „a“, „b“ oder „andere“ sein, und wenn „andere“, dann muss das Eingabefeld vorhanden sein.

Sie sollten keinen Listener auf dem Optionsfeld haben und dann den "erforderlichen" Validator des Eingabefelds basierend auf dem Wert der Optionsfelder umschalten.

Ich verstehe nicht, warum Sie glauben, dass die Validierung eine CRUD-API haben sollte. Ich verteidige nichts, ich spreche vernünftig und mit Argumenten. Ich sage nicht, dass Methoden zum Abrufen, Entfernen und Umschalten von Validatoren nicht implementiert werden sollten, ich sage, dass fast jeder hier einen Anwendungsfall für CRUD-Validierungen hat, die eigentlich Anti-Patterns sind. Ein Steuerelement ist eine eigenständige Einheit und ändert seine Validierungsregeln während seiner Lebensdauer nicht. Wenn Steuerelemente abhängig sind, werden sie einer Gruppe zugeordnet, und die Gruppe wird validiert.

danke @lazarljubenovic für deine Kommentare und deine Zeit.
Ich habe das Gefühl, Sie haben sich meinen Stackblitz nicht genau angesehen. Ich wende die "Wrapping"-Logik in der FormGroup für ein Szenario an. Ich habe nur zwei unterschiedliche Szenarien für mein Formular, bei denen sich die Validatoren dynamisch ändern müssen. Und in dieser "Phase", in der ich mich mit der formGroup befasse, sind meine Validatoren bereits "zusammengesetzt", also kein CRUD für mich: D.

Ich kann dieses Problem umgehen und hoffe, dass es eines Tages behoben wird.
167 👍 scheinen einen großen Wunsch nach dieser Funktion auszudrücken.

Wenn Sie Zeit haben, würde ich gerne mehr erfahren:

"Ein Steuerelement ist eine eigenständige Einheit und ändert seine Validierungsregeln während seiner Lebensdauer nicht."

hast du ressourcen dazu?

"Wenn Steuerelemente abhängig sind, werden sie in eine Gruppe eingeordnet und die Gruppe validiert."

Auch das hat seine Grenzen.

Auch nicht in Zusammenhang mit diesem PROBLEM:
Wenn die Gruppe die Validierung der Kontrollen übernimmt, werden die Fehler der Gruppe zugeschrieben und spiegeln sich daher nicht visuell auf den Kontrollen wider. Also müssen wir die Fehler manuell auf die untergeordneten Steuerelemente setzen und entfernen. (Das ist ein großer Schmerz im * ).

Ich wünsche allen ein schönes Wochenende.

Ich verstehe auf jeden Fall die Perspektive von @lazarljubenovic .

Ich arbeite jedoch an hochdynamischen, mehrteiligen Unternehmensformularen, die eine komplexe Zusammensetzung erfordern ... viel Verstecken/Anzeigen, Abhängigkeitsketten für Validierungsanforderungen usw.

Die Art und Weise, wie wir mit dieser Komplexität umgehen, besteht darin, valueChanges für alle Formularsteuerelemente, Gruppen oder Arrays zu abonnieren, die für das Auslösen von Abhängigkeiten verantwortlich sind, und dann setValidators oder clearValidators für jedes Szenario zu verwenden. Da es keine Möglichkeit gibt, auf bestimmte Validatoren abzuzielen, speichern wir jeden Satz von Validierungsregeln, die zu einem bestimmten Zeitpunkt gelten könnten, und wenden sie erneut an. Es wäre sicherlich schön, alle Validatoren abrufen, einzelne Validatoren hinzufügen oder bestimmte Validatoren zum Entfernen auswählen zu können, was so aussieht, als könnte alles immer noch mit Blick auf die Unveränderlichkeit entworfen werden.

"Ein Steuerelement ist eine eigenständige Einheit und ändert seine Validierungsregeln während seiner Lebensdauer nicht."

hast du ressourcen dazu?

NÖ; Es ist wirklich nur meine Interpretation der Gestaltung von Formen.

Wenn die Gruppe die Validierung der Kontrollen übernimmt, werden die Fehler der Gruppe zugeschrieben und spiegeln sich daher nicht visuell auf den Kontrollen wider.

Um ehrlich zu sein, ist es sogar in Bezug auf die Benutzeroberfläche zweifelhaft, wohin diese Fehler führen sollen. Wenn zwei Felder nicht zusammenspielen, aber in der Form weit auseinander liegen, wo _soll_ man den Fehler schreiben? Es scheint mir, als sollten Sie einen Schritt zurücktreten und zuerst die UX des Formulars korrigieren.

Natürlich sind Formulare ein wildes Tier und sie können nicht für jeden Anwendungsfall passen. Wenn ich ein SEHR komplexes Formular mit _wirklich_ verrückten Steuerelementen habe, verwende ich @angular/forms überhaupt nicht. Ich sehe @angular/forms als ein großartiges Dienstprogramm, das 95 % der Anwendungsfälle abdeckt und es mir ermöglicht, schnell und ohne großen Aufwand ein Formular zu erstellen.

So wie ich gerne Array#map , Array#filter und Array#reduce verwende, ist manchmal eine gute alte for -Schleife mit einer Indexvariablen die beste Lösung für eine komplexere Iteration .

Ich möchte nur noch einmal klarstellen, dass ich nicht _gegen_ bin, diese Methoden hinzuzufügen; Ich habe einfach das Gefühl, dass die _Mehrheit_ der Leute sie missbrauchen würde. Vielleicht wäre es eine Fußwaffe. Andererseits glaube ich nicht, dass ein nützliches Framework nicht hinzugefügt werden sollte, nur weil jemand es missbrauchen kann. Sie können Ihre Zähne auch mit Toilettenwasser putzen und wir verbieten deswegen keine Toiletten.

Wenn ich ein SEHR komplexes Formular mit wirklich verrückten Steuerelementen habe, verwende ich @angular/forms überhaupt nicht. Ich sehe @angular/forms als ein großartiges Dienstprogramm, das 95 % der Anwendungsfälle abdeckt und es mir ermöglicht, schnell und ohne großen Aufwand ein Formular zu erstellen.

Ist das die offizielle Marktpositionierung der Angular Forms? Ich dachte eher an ein Allzweckwerkzeug, das für große Projekte nützlich ist.

Wenn ich ein SEHR komplexes Formular mit wirklich verrückten Steuerelementen habe, verwende ich @angular/forms überhaupt nicht. Ich sehe @angular/forms als ein großartiges Dienstprogramm, das 95 % der Anwendungsfälle abdeckt und es mir ermöglicht, schnell und ohne großen Aufwand ein Formular zu erstellen.

Ist das die offizielle Marktpositionierung der Angular Forms? Ich dachte eher an ein Allzweckwerkzeug, das für große Projekte nützlich ist.

"Reaktive Formulare" sind für die komplizierten, hochgradig angepassten Anwendungsfälle gedacht ... wie zum Beispiel die Möglichkeit, Ihre Validierung vollständig zu kontrollieren ;)

Sie können es vollständig kontrollieren. Sie müssen nur genau bestimmen, WAS Sie validieren: eine Gruppe oder eine Kontrolle.

hat dies eine traktion gemacht? Ich würde wirklich gerne meine Validatoren mit nur einer formGroup als Eingabe dynamisch ändern, um alles entkoppelt zu halten!!! Reaktive Formulare sind großartig, aber das ist mein einziges Argument.

Sooooooooo, wie soll ich meinen Code testen, der dynamisch Validatoren hinzufügt?

Es scheint, dass der offensichtliche Komponententest nicht funktioniert, da ich keinen der Steuerelemente anwendenden Validatoren _lesen_ kann.

_das geht nicht_
expect(component.formControl.validators.length).toEqual(2);

Aktualisieren :
Eigentlich kann man das so machen. Das Lesen von Validatoren wäre jedoch offensichtlich billiger:

component.myCtrl.setValue('');
expect(component.myCtrl.errors.required).toBeTrue();

Ich bin ein großer Fan von Reactive Forms und liebe, was ich damit machen kann, aber kleine Dinge wie das FormControl nicht nach seinen Validatoren fragen zu können, scheinen ein Versehen zu sein. Angesichts der Tatsache, dass es so viele Zustandseigenschaften auf einem FormControl gibt, was hindert zumindest daran, eine .required -Eigenschaft verfügbar zu machen? Stellen Sie sich vor, das wäre im Alltag so...

Ich: Hallo, ich bin wegen meines Termins hier.
Empfangsdame: Tut mir leid, dass wir Sie nicht sehen können.
Ich: Warum nicht? Ich habe jetzt einen Termin.
Rezeptionistin: Achselzucken , ich weiß nicht.

Ich verallgemeinere ein bisschen, aber wenn ich die FormControl-Eigenschaftswerte übergebe, sollte ich auch auf diese Werte zugreifen können.

was zumindest daran hindert, eine erforderliche Eigenschaft verfügbar zu machen

Die Tatsache, dass Sie Ihren eigenen required Validator erstellen können. Angular kann nicht wissen, was Ihre Funktion tut.

100% verstanden, ich sage nicht, dass es auf einer Funktion basiert. Zu wissen, dass ein Feld erforderlich ist, ist genauso grundlegend wie das Wissen, dass es aktiviert/deaktiviert ist, was konfigurierbar, zugänglich und in der Benutzeroberfläche widergespiegelt ist.
Gibt es irgendetwas, das getan werden kann, damit Entwickler ähnliche Funktionen für ein erforderliches Feld erhalten, damit sie beispielsweise erforderliche Markierungen bedingt anzeigen können, indem sie einfach mit dem FormControl arbeiten?

Hallo, das ist unser neuster Hack, um Eingaben automatisch als erforderlich zu markieren: https://stackblitz.com/edit/material-input-auto-required-asterisk. Dasselbe Konzept kann für mat-select oder jede andere Bibliothek gelten.

const _clearValidators = AbstractControl.prototype.clearValidators;
AbstractControl.prototype.clearValidators = function(){
  (this as any).isRequired = false;
  _clearValidators.call(this);
}

const _setValidators = AbstractControl.prototype.setValidators;
AbstractControl.prototype.setValidators = function(newValidator: ValidatorFn | ValidatorFn[] | null): void {
  (this as any).isRequired = false;
  _setValidators.call(this, newValidator);
}

export function isRequired(control: AbstractControl): ValidationErrors | null{
  (control as any).isRequired = true;
  return Validators.required(control);
}

@Directive({ selector: '[matInput]:not([required])' })
export class MatInputRequiredDirective implements DoCheck {
  constructor(private readonly input: MatInput) { }

  ngDoCheck() {
    const isRequired = (this.input.ngControl && this.input.ngControl.control as any).isRequired || false;
    if(isRequired !== this.input.required){
      this.input.required = isRequired;
      this.input.ngOnChanges();
    }
  }
}

100% verstanden, ich sage nicht, dass es auf einer Funktion basiert. Zu wissen, dass ein Feld erforderlich ist, ist genauso grundlegend wie das Wissen, dass es aktiviert/deaktiviert ist, was konfigurierbar, zugänglich und in der Benutzeroberfläche widergespiegelt ist.

Ich bin mir nicht sicher. „Erforderlichkeit“ ist sehr spezifisch für die Geschäftslogik der Anwendungen. Übergibt eine leere Zeichenfolge eine "erforderliche" Bedingung? Was ist mit einer Null? Ein leeres Array für eine Mehrfachauswahl? False für ein Kontrollkästchen? Null, undefiniert?

Gibt es irgendetwas, das getan werden kann, damit Entwickler ähnliche Funktionen für ein erforderliches Feld erhalten, damit sie beispielsweise erforderliche Markierungen bedingt anzeigen können, indem sie einfach mit dem FormControl arbeiten?

Sie können Fehler bereits basierend auf einer beliebigen Bedingung bedingt ein-/ausblenden. Das Vorhandensein des Fehlers bedeutet nicht unbedingt, dass Sie ihn irgendwo anzeigen müssen. Ich sehe nicht, wie Ihnen das Abfragen von Validatoren anstelle des einfachen Abfragens von Fehlern (was verfügbar ist) hier helfen würde.

Ich bin mir nicht sicher. „Erforderlichkeit“ ist sehr spezifisch für die Geschäftslogik der Anwendungen. Übergibt eine leere Zeichenfolge eine "erforderliche" Bedingung? Was ist mit einer Null? Ein leeres Array für eine Mehrfachauswahl? False für ein Kontrollkästchen? Null, undefiniert?

Ich könnte argumentieren, dass ein Feld, das aktiviert/deaktiviert wird, auch sehr spezifisch für die Geschäftslogik einer Anwendung ist, aber das FormControl bietet Funktionen für einen Entwickler, um dies über den Konstruktor und die Accessoren zu verwalten. Erforderlich zu sein ist eine unscharfe Linie und ich verstehe, woher Sie kommen. Dies kann eine persönliche Präferenz sein, aber es ist sowohl ein Präsentationsstatus (wie aktiviert/deaktiviert, gültig/ungültig) als auch ein Hinweis auf die Gültigkeit des Werts.

Sie können Fehler bereits basierend auf einer beliebigen Bedingung bedingt ein-/ausblenden. Das Vorhandensein des Fehlers bedeutet nicht unbedingt, dass Sie ihn irgendwo anzeigen müssen. Ich sehe nicht, wie Ihnen das Abfragen von Validatoren anstelle des einfachen Abfragens von Fehlern (was verfügbar ist) hier helfen würde.

Ja, ich kann Fehler bedingt ausblenden/anzeigen, aber sobald diese Fehler behoben sind, sind alle Präsentationsteile, die vom erwarteten Zustand abhängen, nicht mehr verfügbar. Ich kann kein Sternchen basierend auf den Validatoren anzeigen, da diese Validatoren, sobald sie zufrieden sind, nicht mehr verfügbar sind.
Ich denke, im Moment muss ich einfach damit umgehen.

Ein Feld, das aktiviert/deaktiviert wird, ist auch sehr spezifisch für die Geschäftslogik einer Anwendung

Ich kann nicht sehen, wie.

Ich kann kein Sternchen basierend auf den Validatoren anzeigen

Fügen Sie stattdessen einen Validator hinzu, der auf Ihrer eigenen "erforderlichen" Semantik basiert. Nicht alles kann ein Einzeiler sein.

Ich suche nach einer vernünftigen Möglichkeit, das Szenario zu unterstützen, nicht unbedingt nach einem Einzeiler. Ich möchte auch Ihre Perspektive verstehen, falls meine falsch ausgerichtet ist.

Was wäre, wenn meine Semantik nur Vanilla wäre und nur benötigt würde, um die Validators.required-Validierung zu nutzen? Wenn ich es im StackBlitz-Beispiel unten zu einem Textfeld hinzufüge und einen Wert eingebe, kann ich es nur über das field.errors -Objekt erfahren und nach der Eigenschaft suchen, die den Validatoren zugeordnet ist. Wenn der Validator jedoch zufrieden ist, existiert diese Eigenschaft nicht mehr. Vielleicht ist mein Ansatz falsch, gibt es eine bessere Möglichkeit, das FormControl zu nutzen, um dies zu erreichen, das ich nicht ausnutze?

StackBlitz-Beispiel

Sie sollten Validatoren nicht basierend auf der Gültigkeit eines anderen Felds umschalten. Erstellen Sie eine Gruppenvalidierung und überprüfen Sie den Wert des Objekts, das sowohl den Namen als auch den sekundären Namen enthält. Weitere Informationen finden Sie in diesem Kommentar .

Was den Stern angeht ... Nun, Ihr Ansatz ist nicht "falsch" in dem Sinne "das ist eine schreckliche Idee", aber es ist eine Idee, die nicht mit der Art und Weise funktioniert, wie Validatoren derzeit vorgestellt werden. Ich würde also eher sagen, dass Ihr Ansatz _inkompatibel_ ist mit der Art und Weise, wie Formulare derzeit verwendet werden sollen.

Sie müssen Ihre eigene Mini-Formularerstellungsbibliothek erstellen, wenn Sie den beschriebenen Effekt schnell erzielen möchten. Hier ist eine kurze Skizze, wie ich das Problem angehen würde, wenn ich mit diesem Problem konfrontiert wäre. Beispielsweise können Sie eine Funktion createForm haben, die ein spec -Objekt wie dieses akzeptiert:

spec = {
  name: {
    initialValue: '',
    isRequired: (formValue) => true, // always required
  },
  secondName: {
    initialValue: '',
    isRequired: (formValue) => isFilledIn(formValue.name), // required only if name is filled in
  }

isFilledIn ist Ihre Funktion, die bestimmt, ob ein Wert ausgefüllt wird oder nicht. Wahrscheinlich so etwas wie val => val != null && value != '' .

Dieses createForm würde dasselbe Formular zurückgeben, als ob Sie es so erstellt hätten:

this.fb.group({
  name: [''],
  secondName: [''],
}, {
  validators: [
    (group) => {
      const value = group.value
      const errors = {}
      if (spec.name.isRequired(value) && !isFilled(value.name)) {
        errors['name-required'] = true
      }
      if (spec.secondName.isRequired(value) && !isFilled(value.secondName)) {
        errors['secondName-required'] = true
      }
      return errors // or null if no errors found
    }
  ]
})

Die Fehler liegen nun in der Gruppe selbst. Der Fehler für secondName gehört tatsächlich dorthin – Sie können secondName nicht alleine validieren, da seine Gültigkeit von einem anderen Feld abhängt (also validieren Sie stattdessen die Gruppe dieser Felder). Der Fehler für name sollte zu name gehören, aber diese Implementierungsskizze würde ihn in die Formulargruppe setzen. Dies könnte für Sie funktionieren oder auch nicht. Sie könnten ein Feld für das Objekt spec einführen, das die Konfiguration wie folgt weiter optimieren würde.

In der Vorlage verwenden Sie nun wie gewohnt Ihr Formular, da Sie das Objekt @angular/forms zur Hand haben. Sie müssen einige Fehler aus der Formulargruppe und einige aus den Steuerelementen drucken (oder vielleicht alle aus der Gruppe, wenn das für Sie funktioniert). Die Bedingung für das Umschalten des Sterns wäre spec[key].isRequired(form.value) .

Die Möglichkeiten, wie genau Sie dies implementieren werden, sind endlos, und alles hängt von so kleinen Entscheidungen ab, dass ich nicht glaube, dass eine Formular-API alle zufrieden stellen würde. Je nachdem, was Sie von einem Formular in Ihrem eigenen Projekt erwarten, ist es meiner Meinung nach am besten, einen „semantischen“ Wrapper um den Formularersteller herum einzuführen, der basierend auf Ihren Anforderungen zusätzliche Dinge erledigt.

Vielen Dank für den Ansatz und dafür, dass Sie sich die Zeit genommen haben, auf den vorherigen Kommentar zu verweisen, der Threadverlauf ist ziemlich lang :)

Ich denke, das ist, wo ein Teil meines Problems war; Richtiges Verwalten von Feldabhängigkeiten zwischen gruppierten und außerhalb der Gruppe befindlichen Steuerelementen. Der Spezifikationsansatz war in den meisten Bereichen mein Fallback, daher bin ich froh, dass Sie ihn anscheinend auch so angehen würden.

dass ich nicht glaube, dass irgendeine Formular-API alle zufriedenstellen würde

Stimmt, ich bin mir nicht sicher, ob eine bereitgestellte API diese Art von Abdeckung erfüllt!

Irgendein Update? Fast 4 Jahre seit der Erstellung dieser Ausgabe.
Wir erstellen derzeit benutzerdefinierte Hilfsfunktionen zum Speichern von Validierungen, Funktionen, die zu sehr von der 'Vanille'-Winkelart abgeleitet sind, nur um Validators eines FormControls zu erhalten.

würde diese Funktion wirklich brauchen

Dies wäre sehr hilfreich für Benutzer, die reaktive Formulare verpacken

Sehr wertvoller Faden. Ich bin jedoch etwas verwirrt darüber, welchen Zweck es erfüllt, indem es Validatoren keinen Zugriff auf ein Formularsteuerelement gewährt. Ich bin auf diese Anforderung gestoßen, bei der wir unsere benutzerdefinierte Komponente haben, die ein "*" anzeigen muss, wenn es erforderlich ist, und möglicherweise eine relevante Fehlermeldung direkt darunter (wenn der Benutzer das möchte). Die andere Alternative fügt der API des Steuerelements selbst hinzu, indem der Benutzer auch eine Eigenschaft aus der Vorlage festlegen muss. Dies sollte jedoch nicht erforderlich sein, da der Benutzer in einer reaktiven Form bereits seinen erforderlichen Validator (oder für diese Angelegenheit einen anderen Validator) bereitgestellt hat. Meine benutzerdefinierte Komponente sollte in der Lage sein zu wissen, dass ein solcher Validator gesetzt ist, und könnte dann darauf basierend Elemente ein-/ausblenden. Scheint eine sehr häufige Anforderung zu sein.

Eine Lösung mit einem benutzerdefinierten FormControl mit benutzerdefinierter Konfiguration, um alle Informationen zu Validatoren und möglicherweise die vollständige Kontrolle über diese Validatoren zu haben:

export class CustomFormControl extends FormControl {

  public listValidator: CustomValidator[] = [];

  private constructor(formState?: any,
                      validatorOrOpts?: ValidatorFn | ValidatorFn[] | CustomCtrlOpts | null,
                      asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) {

    super(formState, validatorOrOpts as ValidatorFn[], asyncValidator);

    if (validatorOrOpts instanceof CustomCtrlOpts) {
      this.listValidator = validatorOrOpts.customConf;
    }
  }

  // Builder ith default value
  public static init(formState?: any): CustomFormControl {
    return new CustomFormControl(isNil(formState) ? null : formState);
  }

  public required(): CustomFormControl {
    return this.and('required');
  }

  public email(): CustomFormControl {
    return this.and('email');
  }

  public custom(): CustomFormControl {
    return this.and('custom');
  }

  public min(min: number): CustomFormControl {
    return this.and('min', min);
  }

  public max(max: number): CustomFormControl {
    return this.and('max', max);
  }

  private and(key: TypeCustomValidator, value?: any): CustomFormControl {
    this.listValidator.push({key: key, value: value});
    this.setValidators(new CustomCtrlOpts(this.listValidator).validators as ValidatorFn[]);
    return this;
  }
}

// Add others if needed
export type TypeCustomValidator = 'min' | 'max' | 'required' | 'email' | 'custom' ;

export interface HandledValidator {
  key: TypeCustomValidator;
  function: Function;
  params?: boolean;
}

export const listHandledValidators: HandledValidator[] = [
  {key: 'required', function: Validators.required},
  {key: 'email', function: Validators.email},
  {key: 'min', function: Validators.min, params: true},
  {key: 'max', function: Validators.max, params: true},
  {key: 'custom', function: custom}
];

export interface CustomValidator {
  key: TypeCustomValidator;
  value?: any;
}

export class CustomCtrlOpts implements AbstractControlOptions {

  validators?: ValidatorFn | ValidatorFn[] | null;
  asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
  updateOn?: 'change' | 'blur' | 'submit';

  private _customConf?: CustomValidator[];

  constructor(value: CustomValidator[]) {
    this.customConf = value;
  }

  get customConf(): CustomValidator[] {
    return this._customConf;
  }

  set customConf(value: CustomValidator[]) {

    this._customConf = value;

    if (!value) {
      return;
    }

    this.validators = [];

    value.forEach(customValidator => {
      const validator = listHandledValidators.find(it => it.key === customValidator.key);
      if (validator.params) {
        (this.validators as ValidatorFn[]).push(validator.function(customValidator.value) as ValidatorFn);
      } else {
        (this.validators as ValidatorFn[]).push(validator.function as ValidatorFn);
      }
    });
  }
}

mit FormGroup-Initialisierung wie

this.form = this.fb.group({
    amount: CustomFormControl.init(10).required().min(0), 
    ...

Dies wäre ein wirklich nettes Feature zu haben. Die Verwendung von Problemumgehungen dafür fühlt sich wie Zeitverschwendung an.

würde diese Funktion wirklich brauchen

Ja das ist wirklich nötig. Denken Sie einfach, wenn es einen Weg gibt, etwas einzustellen , dann muss es auch einen ähnlichen Weg geben , oder? Wenn Sie etwas leicht einstellen können, aber viel Mühe aufwenden müssen, um es zurückzubekommen, dann fehlt eindeutig etwas, das _wirklich benötigt_ wird.

Irgendwelche Fortschritte bei diesem Problem/Feature?

@BenKatanHighLander Es ist unterwegs: https://github.com/angular/angular/pull/37263#issuecomment -723333060

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen