Make writing a habit together! This is the 8th day of my participation in the “Gold Digging Day New Plan · April More Text Challenge”. Click here for more details

Due to the recent upgrade of the company’s framework, the original way of checking forms manually has been abandoned and all forms have been changed to response-based. Since I haven’t used them before, I thought I was the only one who didn’t use them at first. After knowing other colleagues in the team, I learned that they were not familiar with them

After the time is relatively tight, can not only do while learning while changing, so it is inevitable to step on some pits, of course, also spent some time to learn, although it may be very simple for familiar people, but still will learn the process and summary and the method of solving the problem summed up, is also a kind of refinement. Here is more theory combined with practical business requirements, rather than blindly in accordance with the official documentation of the WAY to write API introduction, if that is learning notes, rather than summary.

Why focus on reactive forms? Because reactive forms provide direct, explicit access to the underlying form object model. They are more robust than template-driven forms: they are more extensible, reusable, and testable. It’s good for complex forms, but most importantly, I don’t know anything else.

Basic concepts of reactive forms

1.FormControl, FormMarRay, FormGroup

FormControl: used to track the value and validation status of a single FormControl, such as a field binding

// Initializes a field value as the test name and is not available
const Name:FormControl = new FormControl({value:'Test name'.disabled: true });
Copy the code


2.FormArray: Used to track the value and state of an array of form controls, such as several columns together, commonly used tables, or embedded tables in a form

// Define the attribute of the form object as FormArray of the aliases
this.validateForm = this.fb.group({aliases: this.fb.array([]),});

/ / get FormArray
get aliases() {return this.validateForm.get('aliases') asFormArray; }// Add item to FormArray
this.aliases.push(
  this.fb.group({Id: 0.Name: [null]}));Copy the code


3. FormGroup: FormControl is used to track the value and validation status of a single FormControl. It can contain one or more FormControl and FormArray forms. Typically, a form corresponds to a FormGroup instance, and the fields of the form correspond to FormControl and FormArray forms. For example, form Groups can be nested in Form Ray, which is such flexibility.

validateForm =  new FormGroup({Name: new FormControl({value:'Test name'.disabled: true})}); validateForm =this.fb.group({});
Copy the code


4. FormBuilder: FormBuilder is an injectable service provider. Creating multiple FormControl instances manually can be tedious. FormBuilder provides a convenient way to generate form controls. Using the Group method of FormBuilder reduces the amount of repetitive code and, in other words, facilitates the generation of forms

validateForm! : FormGroup;// Create it manually
validateForm = new FormGroup({
    Name: new FormControl('Test name')});//FormBuilder
validateForm = this.fb.group({
  Name:[ { value:'Test name'.disabled:true}]});Copy the code




2.Validator form validation

Form validation is used to ensure that user input is complete and correct. How to add a single validator to a form control and how to display the state of the form as a whole, usually the validator returns NULL to indicate that all validations passed.


1. Synchronous validator: The synchronous validator function takes an instance of a control and returns a set of validation errors or NULL, which can be passed in as a second argument when FormControl is instantiated

// The formControlName value must be the same as the FormControl instance in the TS code<input type="text" id="name" class="form-control" formControlName="name" required>// Check whether the corresponding FormControl fails the verification and has an error message<div *ngIf="name.errors? .['required']">
    Name is required.
 </div>
Copy the code
// Initializes a field and adds a required verification validator
const name:FormControl = new FormControl({'Test name'.disabled: true },Validators.required,);

// Get this FormControl
get name() { return this.heroForm.get('name'); }
Copy the code


Asynchronous validators: An asynchronous function accepts an instance of a control and returns a Promise or Observable. Angular doesn’t run the asynchronous validator until all synchronous validators pass, passing it in as a third argument when it instantiates FormControl

3. Built-in validators: For example, validates some length that cannot be null using the provided Validator class


4. Custom validators: The internal validators provided by the system cannot meet existing requirements. You can use custom validators to perform personalized verification

// The formControlName value must be the same as the FormControl instance in the TS code<input type="text" id="name" class="form-control" formControlName="name" required>// Check whether the corresponding FormControl fails the verification and has an error message<div *ngIf="name.hasError('Invalid')">The name is too long....</div>
Copy the code
// Initializes a field and adds a required verification validator
const name:FormControl = new FormControl({'Test name'.disabled: true },this.CustomValidators());

CustomValidators() {
 return (control: AbstractControl): ValidationErrors | null= > {
    if(control.value.length! =10)
      {
        return {Invalid:true}}return null;
    };
}
Copy the code




3. Basic methods and attributes of forms and elements


  • methods
methods Use effect
setValue() SetVlue allows you to set the value of the FormControl control. However, all the FormGroup properties must be assigned together.
patchValue() PatchValue can also be used to set the value of the FormControl. You can set the specified FormControl as required without setting all of it. It is often used to update a field value
reset () Reset ({value: ‘Drew’, disabled: true}); reset({value: ‘Drew’, disabled: true});
markAsPristine() Error-form (); error-form (); error-form (); error-form (); error-form ()
markAsDirty() Is to mark the FormControl value as changed, with its state Dirty being true
updateValueAndValidity() Recalculate the value of the FormControl, validate state, and so on
setValidators() Set validators for the FormControl, or arrays if multiple.”setValidators([v1,v2,v3])
disable() The FormControl is disabled. Note that when FormControl is disabled, the normal value getValue() of the form is null. GetRawValue () takes the original value object to get the FormControl value
enable() Set enable for the FormControl


  • attribute
attribute Instructions for use
touched A FormControl touched value of true indicates that the control is in focus, and vice versa
untouched When Amanda holds true it does not hold focus and vice versa
pristine To indicate that the form element is pristine and the user hasn’t acted on it, you can set this to true using the markAsPristine method
dirty Indicates that form elements have been manipulated by the user and can be set to true using the markAsDirty method
status Gets the state of the FormControl
Errors Gets error information for the current control




2. Case analysis and application

1. Simple form implementation

Demand # # # # # # 1

The main framework version we use is Angular 12 + ng-Zorro, so a lot of the implementation and sample code below will be related to them. The code may be different, but only slightly different at the UI level. In fact, the requirements in the following examples are basically some basic contents and problems I need to do in my work. The ideas and process of solving them are the same even in the screenshots after consulting the materials.

Implement the most basic new functions of the form and verify that the employee ID is mandatory and the length cannot exceed 50. The effect picture to be realized is as follows

Analysis of the

1. First of all, there is no special point to pay attention to the requirements, and it is basically simple input box assignment and then save, as long as the basic concept is clear to realize this is the simplest

2. We use one FormGroup and six formControls to bind the interface

3. Binding validator used for verification length and mandatory

Implementation steps

1. Define the HTML form structure

<! -- formGroup binds the form object -->
<form nz-form [formGroup] ="validateForm" nzLayout="vertical">
  <nz-form-label nzRequired>Employee ID
  </nz-form-label>
  
   <! -- Employee_ErrorTrip Indicates a message that indicates that the authentication fails -->
   <! -- formControlName Bind form element FormControl -->
  <nz-form-control [nzErrorTip] ="Employee_ErrorTrip">
    <input nz-input formControlName="EmployeeID"  placeholder="" />
  </nz-form-control>

  <ng-template #Employee_ErrorTrip let-control>
    <ng-container *ngIf="control.hasError('required')">Employee number is mandatory</ng-container>
  </ng-template>
</form>
Copy the code

2. Declare form objects in TypeScript code, inject FormBuilder in constructors, and initialize forms in ngOnInit

// Define the form object
validateForm:FormGroup;

// The constructor is injected into FormBuilder
constructor(privateFb: FormBuilder) {}// Initialize the form in the declaration cycle hook function
ngOnInit() {
  //Initialize and bind mandatory and length validatorsthis.validateForm = this.fb.group({
      EmployeeID: [' ', [Validators.required, Validators.maxLength(50)]],})}Copy the code


2. Apply the form to the table
Demand for 2

Forms need to be added and submitted as well as personalized customization requirements. The renderings and requirements to be realized are described as follows

1. Click Add to Add a row, click Save to Save the data, and click Revoke to cancel the edit

2. The default start time and end time are disabled

3. When Contract Type is Set to Short-term Contract, Contract Start Date and Contract End Date are available; when Contract Type is set to Long-term Contract, they are unavailable

4. If the Contract start date and Contract end date are available, verify the validity of the start and end time. For example, the start event cannot exceed the end time

Analysis of the

1. Use the form in the table. Although the form is in the table, each column is also a FormControl

2. There are four columns for input values, so there are four FormControl columns and then the last column is two buttons

FormControl can not be used alone, so it needs to be wrapped by FormGroup

4. We know that our table has multiple rows, that is, multiple formgroups. We can use FormArray to store it, because it represents a group of form groups

5. According to point 2, the default start time and end time are forbidden. We know that it is ok to set the FormControl corresponding to the start and end time to Disabled at the beginning of initialization

6. The third requirement involves linkage, that is, when the FormControl corresponding to Contract Type is set to “short-term Contract”, the FormControl corresponding to “Start and end time” should be set to available, which needs to be completed by a custom validator

Implementation steps

1. First define the Html form structure

<nz-table [nzData] ="CONTRACTS" nzTableLayout="fixed" [nzShowQuickJumper] ="true">
  <thead>
    <tr>
      <th>Contract type</th>
      <th>Contract start date</th>
      <th>Contract end date</th>
      <th>Agreement item</th>
      <th>Operation</th>
    </tr>
  </thead>

  <tbody>
    <! -- Bind form group property aliases -->
    <ng-container formArrayName="aliases">
      <! Bind the index of the current row in the formGroup to the formGroup -->
      <tr [formGroupName] ="i" *ngFor="let data of aliases.controls; index as i">
        <td>
          <nz-form-item>
            <nz-form-control nzSpan="1-24">
              <! -- AccountName binding FormControl -->
              <nz-select nzShowSearch nzAllowClear nzPlaceHolder="" formControlName="Type">
                <nz-option *ngFor="let option of Type" [nzValue] ="option.Code" [nzLabel] ="option.Value">
                </nz-option>
              </nz-select>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-control nzSpan="1-24" [nzErrorTip] ="StartDate">
              <nz-date-picker id="StartDate" formControlName="StartDate" nzPlaceHolder="">
              </nz-date-picker>
              <! -- Validation prompt template for time validator -->
              <ng-template #StartDate let-control>
              	<! -- Check whether the time validator has the beginGtendDate attribute, and display the prompt message if it does not pass the validation.
                <ng-container *ngIf="control.hasError('beginGtendDate')">The start time cannot be later than the end time</ng-container>
              </ng-template>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-control nzSpan="1-24" [nzErrorTip] ="EndDate">
              <nz-date-picker style="width: 100%;" formControlName="EndDate" nzPlaceHolder="">
              </nz-date-picker>
              <ng-template #EndDate let-control>
                <ng-container *ngIf="control.hasError('beginGtendDate')">The start time cannot be later than the end time</ng-container>
              </ng-template>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-control nzSpan="1-24">
              <nz-select nzShowSearch nzAllowClear nzPlaceHolder="" formControlName="ContractType">
                <nz-option *ngFor="let option of ContractTypes" [nzValue] ="option.Code" [nzLabel] ="option.Value">
                </nz-option>
              </nz-select>
            </nz-form-control>
          </nz-form-item>
        </td>
        <td>
          <button style="color: #009688;" nz-button nzType="text">
            <i nz-icon nzType="save"></i>Save
          </button>
          <button nz-button nzType="text" nzDanger>
            <i nz-icon nzType="redo"></i>Revoke
          </button>
        </td>
      </tr>
    </ng-container>
  </tbody>
</nz-table>
Copy the code

2. Declare the form object validateForm in TypeScript code and initialize an instance of the FormArray property Aliases as the formArrayName value

3. Add a data entry to the aliases property of the validateForm object when clicking the Add button

4. Define the custom validator contractTypeValidation() method for Contract Type linkage

5. Define the timevalidator () method. If the time is invalid, set the FormControl error state to the beginGtendDate property, and then use this property to render the Japanese message in the template

// Define the form object
validateForm:FormGroup;
// The constructor is injected into FormBuilder
constructor(privateFb: FormBuilder) {}// Initializes the form object validateForm in the declaration periodic hook function
ngOnInit() {
this.validateForm = this.fb.group({
      aliases: this.fb.array([]),
 	});
}
// Declare the aliases property to be used as the interface formArrayName binding
get aliases(){
	return this.validateForm.get('aliases') as FormArray;
}

addNewRow() {
  const group = this.fb.group({
    //Add to the Type initializer validator Type: [null, [CommonValidators.required, this.contractTypeValidation()]],
    //FormControl StartDate: [{value:null, disabled: true }, []],
    EndDate: [{ value: null, disabled: true },[]],
    ContractType: [null, [CommonValidators.required, CommonValidators.maxLength(20)]],})this.aliases.push(group);
}

  // Define a Contract Type validator
contractTypeValidation() {
    return (control: AbstractControl): ValidationErrors | null= > {let contents: any[] = this.validateForm.value.aliases;
      if(control.touched && ! control.pristine) {//Get the Form groupconst formArray: any = this.validateForm.controls.aliases;
        //Finds the index of the row being editedconstindex = contents.findIndex((x) => ! x.isShowEdit);//Get the start and end time FormControl instanceconst StartDate: AbstractControl =
          formArray.controls[index].get('StartDate'),
          EndDate: AbstractControl = formArray.controls[index].get('EndDate');

        if (control.value === "Short term contract") {
          //To begin to end time set validation is used to verify legitimacy StartDate. SetValidators ([CommonValidators required,this.timeValidation()]);
          EndDate.setValidators([this.timeValidation()]);

          //Start endDate.enable (); StartDate.enable(); }else {
          //Contract Type is not a short-term Contract is to remove the validator StartDate. ClearValidators (); EndDate.clearValidators();//Disable the start and end time enddate.disable (); StartDate.disable(); }}return null; }}// Custom time validator
 timeValidation()
  {
    return (control: AbstractControl): ValidationErrors | null= > {if(! control.pristine) {let contents: any[] = this.validateForm.value.aliases;
        const formArray: any = this.validateForm.controls.aliases;
        constindex = contents.findIndex((x) => ! x.isShowEdit);//Get the start and end time FormControl instanceconst EndDate: string = formArray.controls[index].get('EndDate').value;
        const StartDate: string =formArray.controls[index].get('StartDate').value;

        if (EndDate === null || StartDate === null) return null;

        //If the time is illegal, set the current control's error state beginGtendDate totrue
        if (
          Date.parse(control.value) > Date.parse(EndDate) ||
          Date.parse(control.value) < Date.parse(StartDate)
        ) {
          return { beginGtendDate: true}; }}return null; }}Copy the code