forms - कोणीय 2-बड़े पैमाने पर अनुप्रयोग रूपों की हैंडलिंग




angular architecture (3)

आपके दृष्टिकोण और ओवंगल का एक बहुत अच्छा लग रहा है, लेकिन भले ही यह एसओ प्रश्न हल हो गया है, मैं अपने समाधान को साझा करना चाहता हूं क्योंकि यह वास्तव में एक अलग दृष्टिकोण है जो मुझे लगता है कि आप पसंद कर सकते हैं या किसी और के लिए उपयोगी हो सकते हैं।

अनुप्रयोग के व्यापक रूप के लिए क्या समाधान हैं, जहां घटक वैश्विक रूप में विभिन्न उप भागों की देखभाल करते हैं।

हमने उसी सटीक मुद्दे का सामना किया है और विशाल, नेस्टेड और कभी-कभी बहुरूपी रूपों से जूझने के महीनों बाद, हम एक ऐसा समाधान लेकर आए हैं, जो हमें प्रसन्न करता है, जिसका उपयोग करना सरल है और जो हमें "सुपर पॉवर" देता है (जैसे प्रकार TS और HTML दोनों के भीतर सुरक्षा), नेस्टेड त्रुटियों और अन्य तक पहुंच।

हमने इसे एक अलग लाइब्रेरी में निकालने और इसे खोलने का स्रोत तय किया है।
स्रोत कोड यहां उपलब्ध है: https://github.com/cloudnc/ngx-sub-form
और npm पैकेज को उस npm i ngx-sub-form तरह स्थापित किया जा सकता है

पर्दे के पीछे, हमारी लाइब्रेरी ControlValueAccessor का उपयोग ControlValueAccessor और जो हमें इसे टेम्प्लेट रूपों और प्रतिक्रियाशील रूपों पर उपयोग करने की अनुमति देती है (हालांकि आप प्रतिक्रियात्मक रूपों का उपयोग करके इसे सर्वश्रेष्ठ रूप से प्राप्त करेंगे)।

तो यह सब क्या है?

इससे पहले कि मैं समझाना शुरू करूँ, यदि आप उचित संपादक के साथ चलना पसंद करते हैं, तो मैंने एक Stackblitz उदाहरण बनाया है: https://stackblitz.com/edit/so-question-angular-2-large-scale-application-forms-handling

अच्छी तरह से एक उदाहरण 1000 शब्दों के लायक है जो मुझे लगता है कि चलो आपके फॉर्म के एक हिस्से (नेस्टेड डेटा के साथ सबसे कठिन) को फिर से करें: personalDetailsForm$

पहली बात यह सुनिश्चित करें कि सब कुछ सुरक्षित होने जा रहा है। आइए उसके लिए इंटरफेस बनाएँ:

export enum Gender {
  MALE = 'Male',
  FEMALE = 'Female',
  Other = 'Other',
}

export interface Name {
  firstname: string;
  lastname: string;
}

export interface Address {
  streetaddress: string;
  city: string;
  state: string;
  zip: string;
  country: string;
}

export interface Phone {
  phone: string;
  countrycode: string;
}

export interface PersonalDetails {
  name: Name;
  gender: Gender;
  address: Address;
  phone: Phone;
}

export interface MainForm {
  // this is one example out of what you posted
  personalDetails: PersonalDetails;

  // you'll probably want to add `parent` and `responsibilities` here too
  // which I'm not going to do because `personalDetails` covers it all :)
}

फिर, हम एक घटक बना सकते हैं जो कि NgxSubFormComponent विस्तारित NgxSubFormComponent
आइए इसे personal-details-form.component

@Component({
  selector: 'app-personal-details-form',
  templateUrl: './personal-details-form.component.html',
  styleUrls: ['./personal-details-form.component.css'],
  providers: subformComponentProviders(PersonalDetailsFormComponent)
})
export class PersonalDetailsFormComponent extends NgxSubFormComponent<PersonalDetails> {
  protected getFormControls(): Controls<PersonalDetails> {
    return {
      name: new FormControl(null, { validators: [Validators.required] }),
      gender: new FormControl(null, { validators: [Validators.required] }),
      address: new FormControl(null, { validators: [Validators.required] }),
      phone: new FormControl(null, { validators: [Validators.required] }),
    };
  }
}

यहाँ ध्यान देने योग्य कुछ बातें:

  • NgxSubFormComponent<PersonalDetails> हमें टाइप सेफ्टी देने जा रहा है
  • हमें getFormControls विधियों को लागू करना है जो एक सार नियंत्रण (यहां name , gender , address , phone ) से मेल खाते शीर्ष स्तर की कुंजियों के शब्दकोश की अपेक्षा phone
  • हम फॉर्मकंट्रोल (सत्यापनकर्ता, एसिंक्स वैधीटर आदि) बनाने के विकल्पों पर पूरा नियंत्रण रखते हैं
  • providers: subformComponentProviders(PersonalDetailsFormComponent) एक ControlValueAccessor (cf कोणीय डॉक्टर) का उपयोग करने के लिए आवश्यक प्रदाताओं को बनाने के लिए एक छोटी सी उपयोगिता फ़ंक्शन है, आपको बस वर्तमान घटक को तर्क के रूप में पारित करने की आवश्यकता है

अब, name , gender , address , phone की हर प्रविष्टि के लिए जो एक वस्तु है, हम इसके लिए एक उप प्रपत्र बनाते हैं (इसलिए इस मामले में सब कुछ लेकिन gender )।

यहाँ फोन के साथ एक उदाहरण है:

@Component({
  selector: 'app-phone-form',
  templateUrl: './phone-form.component.html',
  styleUrls: ['./phone-form.component.css'],
  providers: subformComponentProviders(PhoneFormComponent)
})
export class PhoneFormComponent extends NgxSubFormComponent<Phone> {
  protected getFormControls(): Controls<Phone> {
    return {
      phone: new FormControl(null, { validators: [Validators.required] }),
      countrycode: new FormControl(null, { validators: [Validators.required] }),
    };
  }
}

अब, इसके लिए टेम्पलेट लिखते हैं:

<div [formGroup]="formGroup">
  <input type="text" placeholder="Phone" [formControlName]="formControlNames.phone">
  <input type="text" placeholder="Country code" [formControlName]="formControlNames.countrycode">
</div>

नोटिस जो:

  • हम <div [formGroup]="formGroup"> को formGroup हैं, यहाँ जो formGroup द्वारा प्रदान किया NgxSubFormComponent आपको इसे स्वयं बनाने की आवश्यकता नहीं है
  • [formControlName]="formControlNames.phone" हम डायनामिक formControlName लिए संपत्ति बाइंडिंग का उपयोग करते हैं और फिर formControlNames उपयोग formControlNames । इस प्रकार का सुरक्षा तंत्र NgxSubFormComponent द्वारा भी पेश किया NgxSubFormComponent है और यदि आपका इंटरफ़ेस कुछ बिंदु पर बदल जाता है (हम सभी रिफैक्टर्स के बारे में जानते हैं ...), न केवल आपके टीएस फॉर्म में लापता गुणों के लिए त्रुटि करेंगे, बल्कि HTML (जब आप एओटी के साथ अनुपालन करते हैं) )!

अगला चरण: आइए PersonalDetailsFormComponent टेम्पलेट का निर्माण करते हैं, लेकिन सबसे पहले उस पंक्ति को TS में जोड़ें: public Gender: typeof Gender = Gender; इसलिए हम सुरक्षित रूप से दृश्य से एनम तक पहुँच सकते हैं

<div [formGroup]="formGroup">
    <app-name-form [formControlName]="formControlNames.name"></app-name-form>

    <select [formControlName]="formControlNames.gender">
    <option *ngFor="let gender of Gender | keyvalue" [value]="gender.value">{{ gender.value }}</option>
  </select>

  <app-address-form [formControlName]="formControlNames.address"></app-address-form>

  <app-phone-form [formControlName]="formControlNames.phone"></app-phone-form>
</div>

ध्यान दें कि हम एक उप घटक के लिए जिम्मेदारी कैसे सौंपते हैं? <app-name-form [formControlName]="formControlNames.name"></app-name-form> यह महत्वपूर्ण बिंदु है!

अंतिम चरण : शीर्ष प्रपत्र घटक का निर्माण किया

अच्छी खबर है, हम भी प्रकार सुरक्षा का आनंद लेने के लिए NgxSubFormComponent का उपयोग कर सकते हैं!

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent extends NgxSubFormComponent<MainForm> {
  protected getFormControls(): Controls<MainForm> {
    return {
      personalDetails: new FormControl(null, { validators: [Validators.required] }),
    };
  }
}

और टेम्पलेट:

<form [formGroup]="formGroup">
  <app-personal-details-form [formControlName]="formControlNames.personalDetails"></app-personal-details-form>
</form>

<!-- let see how the form values looks like! -->
<h1>Values:</h1>
<pre>{{ formGroupValues | json }}</pre>

<!-- let see if there's any error (works with nested ones!) -->
<h1>Errors:</h1>
<pre>{{ formGroupErrors | json }}</pre>

उस सभी से प्राप्त करें: - सुरक्षित रूप टाइप करें - पुन: प्रयोज्य! parents लिए एक पते का पुन: उपयोग करने की आवश्यकता है? निश्चित रूप से, कोई चिंता नहीं - नेस्टेड फॉर्म बनाने के लिए अच्छी उपयोगिताओं, एक्सेस फॉर्म कंट्रोल नाम, फॉर्म वैल्यू, फॉर्म एरर (+ नेस्टेड!) - क्या आपने किसी भी जटिल तर्क पर ध्यान दिया है? कोई वेधशालाएं, इंजेक्शन लगाने की कोई सेवा नहीं ... बस इंटरफेस को परिभाषित करते हुए, एक वर्ग का विस्तार करते हुए, फॉर्म नियंत्रण के साथ एक ऑब्जेक्ट पास करें और दृश्य बनाएं। बस

वैसे, यहाँ मैं जिस चीज़ के बारे में बात कर रहा हूँ उसका लाइव डेमो है:
https://stackblitz.com/edit/so-question-angular-2-large-scale-application-forms-handling

इसके अलावा, उस मामले में यह आवश्यक नहीं था, लेकिन रूपों के लिए थोड़ा और अधिक जटिल, उदाहरण के लिए जब आपको type Animal = Cat | Dog जैसे बहुरूपी वस्तु को संभालने की आवश्यकता होती है type Animal = Cat | Dog type Animal = Cat | Dog हमने एक और क्लास ली है जो कि NgxSubFormRemapComponent लेकिन अगर आपको अधिक जानकारी चाहिए तो आप https://github.com/cloudnc/ngx-sub-form को पढ़ सकते हैं।

आशा है कि यह आपके रूपों को बढ़ाने में आपकी मदद करेगा!

संपादित करें:

यदि आप और आगे जाना चाहते हैं, तो मैंने अभी एक ब्लॉग पोस्ट प्रकाशित की है जिसमें रूपों और ngx-sub-form के बारे में बहुत सी बातें समझाने के लिए यहाँ https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9

जिस कंपनी के लिए मैं काम कर रहा हूं, हम कई रूपों के साथ एक बड़े पैमाने पर एप्लिकेशन विकसित कर रहे हैं, जिसे उपयोगकर्ता को हमारे प्रोग्राम के लिए रजिस्टर करने के लिए भरना होगा। जब सभी प्रश्नों का उत्तर दिया जाता है, तो उपयोगकर्ता एक अनुभाग तक पहुंचता है जो अपने सभी उत्तरों को प्रस्तुत करता है, अमान्य उत्तरों पर प्रकाश डालता है और उपयोगकर्ता को किसी भी पूर्ववर्ती चरण के चरणों को फिर से जारी करने और उनके उत्तरों को संशोधित करने का मौका देता है। इस तर्क को शीर्ष-स्तरीय अनुभागों में दोहराया जाएगा, जिनमें से प्रत्येक में कई चरण / पृष्ठ और एक सारांश पृष्ठ होगा।

इसे पूरा करने के लिए, हमने प्रत्येक अलग-अलग फॉर्म स्टेप के लिए एक घटक बनाया है (वे "व्यक्तिगत विवरण" या "योग्यता" आदि जैसी श्रेणियां हैं) उनके संबंधित मार्गों और सारांश पृष्ठ के लिए एक घटक।

इसे यथासंभव डीआरवाई के रूप में रखने के लिए, हमने एक "मास्टर" सेवा तैयार करना शुरू कर दिया है जो सभी विभिन्न फॉर्म स्टेप्स (मान, वैधता आदि) के लिए जानकारी रखती है।

import { Injectable } from '@angular/core';
import { Validators } from '@angular/forms';
import { ValidationService } from '../components/validation/index';

@Injectable()
export class FormControlsService {
  static getFormControls() {
    return [
      {
        name: 'personalDetailsForm$',
        groups: {
          name$: [
            {
              name: 'firstname$',
              validations: [
                Validators.required,
                Validators.minLength(2)
              ]
            },
            {
              name: 'lastname$',
              validations: [
                Validators.required,
                Validators.minLength(2)
              ]
            }
          ],
          gender$: [
            {
              name: 'gender$',
              validations: [
                Validators.required
              ]
            }
          ],
          address$: [
            {
              name: 'streetaddress$',
              validations: [
                Validators.required
              ]
            },
            {
              name: 'city$',
              validations: [
                Validators.required
              ]
            },
            {
              name: 'state$',
              validations: [
                Validators.required
              ]
            },
            {
              name: 'zip$',
              validations: [
                Validators.required
              ]
            },
            {
              name: 'country$',
              validations: [
                Validators.required
              ]
            }
          ],
          phone$: [
            {
              name: 'phone$',
              validations: [
                Validators.required
              ]
            },
            {
              name: 'countrycode$',
              validations: [
                Validators.required
              ]
            }
          ],
        }
      },
      {
        name: 'parentForm$',
        groups: {
          all: [
            {
              name: 'parentName$',
              validations: [
                Validators.required
              ]
            },
            {
              name: 'parentEmail$',
              validations: [
                ValidationService.emailValidator
              ]
            },
            {
              name: 'parentOccupation$'
            },
            {
              name: 'parentTelephone$'
            }
          ]
        }
      },
      {
        name: 'responsibilitiesForm$',
        groups: {
          all: [
            {
              name: 'hasDrivingLicense$',
              validations: [
                Validators.required,
              ]
            },
            {
              name: 'drivingMonth$',
              validations: [
                ValidationService.monthValidator
              ]
            },
            {
              name: 'drivingYear$',
              validations: [
                ValidationService.yearValidator
              ]
            },
            {
              name: 'driveTimesPerWeek$',
              validations: [
                Validators.required
              ]
            },
          ]
        }
      }
    ];
  }
}

प्रत्येक घटक के लिए HTML फॉर्म बाइंडिंग सेट करने के लिए, संबंधित ऑब्जेक्ट कुंजी तक पहुंच बनाने और नेस्टेड फॉर्म समूह के साथ-साथ सारांश पृष्ठ द्वारा, जिसकी प्रस्तुति परत केवल मॉडल है -> देखें)।

export class FormManagerService {
    mainForm: FormGroup;

    constructor(private fb: FormBuilder) {
    }

    setupFormControls() {
        let allForms = {};
        this.forms = FormControlsService.getFormControls();

        for (let form of this.forms) {

            let resultingForm = {};

            Object.keys(form['groups']).forEach(group => {

                let formGroup = {};
                for (let field of form['groups'][group]) {
                    formGroup[field.name] = ['', this.getFieldValidators(field)];
                }

                resultingForm[group] = this.fb.group(formGroup);
            });

            allForms[form.name] = this.fb.group(resultingForm);
        }

        this.mainForm = this.fb.group(allForms);
    }

    getFieldValidators(field): Validators[] {
        let result = [];

        for (let validation of field.validations) {
            result.push(validation);
        }

        return (result.length > 0) ? [Validators.compose(result)] : [];
    }
}

मास्टर फॉर्म सेवा में निर्दिष्ट प्रपत्र नियंत्रण तक पहुँचने के लिए, हमने घटकों में निम्नलिखित सिंटैक्स का उपयोग करना शुरू किया:

personalDetailsForm$: AbstractControl;
streetaddress$: AbstractControl;

constructor(private fm: FormManagerService) {
    this.personalDetailsForm$ = this.fm.mainForm.controls['personalDetailsForm$'];
    this.streetaddress$ = this.personalDetailsForm$['controls']['address$']['controls']['streetaddress$'];
}

जो हमारी अनुभवहीन आँखों में एक कोड गंध की तरह लगता है। हमारे पास इस बात की प्रबल चिंता है कि इस तरह से कोई एप्लिकेशन कैसे स्केल करेगा, जो कि अंत में हमारे पास सेक्शन की मात्रा होगी।

हम अलग-अलग समाधानों के बारे में चर्चा कर रहे हैं, लेकिन हम एक के साथ नहीं आ सकते हैं जो कि कोणीय के रूप में इंजन का लाभ उठाता है, हमें हमारी मान्यता पदानुक्रम को बरकरार रखने की अनुमति देता है और सरल भी है।

क्या हम जो करने की कोशिश कर रहे हैं उसे हासिल करने का एक बेहतर तरीका है?


क्या सेवा में फ़ॉर्म नियंत्रण रखना वास्तव में आवश्यक है? केवल डेटा के रक्षक के रूप में सेवा को क्यों न छोड़ें, और क्या घटकों में प्रपत्र नियंत्रण है? आप अमान्य डेटा वाले घटक से उपयोगकर्ता को नेविगेट करने से रोकने के लिए CanDeactivate गार्ड का उपयोग कर सकते हैं।

https://angular.io/docs/ts/latest/api/router/index/CanDeactivate-interface.html


मैंने एक समान आवेदन किया। समस्या यह है कि आप एक ही समय में अपने सभी इनपुट बना रहे हैं, जिसकी संभावना नहीं है।

मेरे मामले में, मैंने एक FormManagerService किया था जो फॉर्मग्रुप की एक सरणी का प्रबंधन करता है। प्रत्येक चरण में एक FormGroup है जिसे एक बार प्रारंभ में एक बार आरंभ किया जाता है, जो FormManagerService पर अपने FormGroup कॉन्फिगरेशन को भेजकर स्टेप घटक के ngOnInit पर होता है। ऐसा कुछ:

stepsForm: Array<FormGroup> = [];
getFormGroup(id:number, config: Object): FormGroup {
    let formGroup: FormGroup;
    if(this.stepsForm[id]){
        formGroup = this.stepsForm[id];
    } else {
        formGroup = this.createForm(config); // call function to create FormGroup
        this.stepsForm[id] = formGroup;
    }
    return formGroup;
}

आपको यह जानने के लिए एक आईडी की आवश्यकता होगी कि कौन सा फ़ॉर्मग्रुप कदम से मेल खाता है। लेकिन उसके बाद, आप प्रत्येक चरण में अपने फ़ॉर्म के कॉन्फ़िगरेशन को विभाजित करने में सक्षम होंगे (इसलिए एक छोटी फ़ाइल जो एक बड़ी फ़ाइल की तुलना में रखरखाव के लिए आसान है)। यह प्रारंभिक लोड समय को कम कर देगा क्योंकि फॉर्मग्रुप्स केवल तभी बनाते हैं जब जरूरत होती है।

अंत में सबमिट करने से पहले, आपको बस अपने फॉर्मग्रुप सरणी को मैप करना होगा और वे सभी मान्य होने पर मान्य करना होगा। बस सुनिश्चित करें कि सभी चरणों का दौरा किया गया है (अन्यथा कुछ फॉर्मग्रुप नहीं बनाया जाएगा)।

यह सबसे अच्छा समाधान नहीं हो सकता है लेकिन यह मेरी परियोजना में एक अच्छा फिट था क्योंकि मैं अपने कदमों का पालन करने के लिए उपयोगकर्ता को मजबूर कर रहा हूं। मुझे अपनी प्रतिक्रिया दें। :)





architecture