1. Build dynamic forms

  • Build a dynamic form, dynamically generate controls, and validate rules.

  • Create an input component and a SELECT component

  • Inject the component into the page and display it


Creating a component directory

The first half of the direct reference to the official document writing method, later open personalized customization, powerful!

Create a folder dynamic-form under the theme/ Components directory

The project directory structure is as follows, and we will build it step by step

mark

Creating an object model

We need to define an object model that describes the scenarios required for form functionality. There are many related functions (similar to input Select).

Create a basic base class named QuestionBase with a dynamic-form-base/question-base.ts file

export class QuestionBase<T>{
    value: T; // The type is optional
    key: string; / / the field name
    label: string; // Text prompt before control
    required: boolean; // This parameter is mandatory
    disabled: boolean; // Whether to disable
    reg: string; / / regular
    prompt: string; // Verification failed
    order: number; / / sorting
    controlType: string; // Render type
    constructor(options: { value? : T, key? :string, label? :string, required? :boolean, disabled? :boolean, reg? :string, prompt? :string, order? :number, controlType? :string
    } = {}) {
        // Set the default values for each value
        this.value = options.value;
        this.key = options.key || ' ';
        this.label = options.label || ' ';
        this.required = !! options.required;this.disabled = !! options.disabled;this.reg = options.reg || ' ';
        this.prompt = options.prompt || ' ';
        this.order = options.order || 0;
        this.controlType = options.controlType || ' '; }}Copy the code

From this base, we derive two classes, InputQuestion and SelectQuestion, representing text boxes and drop-down boxes.

The reason for doing this is to personalize, standardize, and dynamically render appropriate components for different controls

InputQuestion can support multiple HTML element types (e.g. text number email) via the type attribute — dynamic-form-base/question-input.ts

import { QuestionBase } from './question-base';

export class InputQuestion extends QuestionBase<string | number> {
    controlType = 'input';
    type: string;

    constructor(options: {} = {}) {
        super(options);
        this.type = options['type'] | |' '; }}Copy the code

A SelectQuestion represents a selection box with an optional list

import { QuestionBase } from './question-base';

export class SelectQuestion extends QuestionBase<string | number> {
    controlType = 'select';
    options: any[] = [];

    constructor(options: {} = {}) {
        super(options);
        this.options = options['options'] || [];
    }
}Copy the code

Finally, don’t forget to export these three models with index.ts

Next, we define a QuestionControlService, a service that transforms the model into a FormGroup. In short, this FormGroup uses metadata from the questionnaire model and allows us to set default values and validation rules

question-control.service.ts

import { Injectable } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

import { QuestionBase } from './dynamic-form-base';

@Injectable(a)export class QuestionControlService {
    constructor() {}// Convert to a control
    toFormGroup(questions: QuestionBase<any> []) {let group: any = {};

        questions.forEach(question= > {
            if (question.required) {    // If the parameter is mandatory
                if (question.reg) {       // There are regular cases
                    group[question.key] = new FormControl(question.value || ' ', Validators.compose([Validators.required, Validators.pattern(question.reg)]));
                } else {
                    group[question.key] = new FormControl(question.value || ' ', Validators.compose([Validators.required])); }}else if(! question.required && question.reg) {// If the option is optional but requires a regular match
                group[question.key] = new FormControl(question.value || ' ', Validators.compose([Validators.pattern(question.reg)]));
            } else {
                group[question.key] = new FormControl(question.value || ' '); }});return newFormGroup(group); }}Copy the code

Dynamic form component

Now that we have a complete model defined, we can start building a component that presents a dynamic form.

DynamicFormComponent is the main container and entry for the form

dynamic-form.component.html

<div>
  <form (ngSubmit) ="onSubmit()" [formGroup] ="form">
    <div *ngFor="let question of questions" class="form-row">
      <df-question [question] ="question" [form] ="form"></df-question>
    </div>

    <div class="form-row">
      <button type="submit" [disabled] =! "" form.valid">save</button>
    </div>
  </form>

  <div *ngIf="payload" class="form-row">
    <strong>Saved the following values</strong><br>{{payload}}
  </div>
</div>Copy the code

dynamic-form.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { QuestionBase } from './dynamic-form-base/question-base';
import { QuestionControlService } from './question-control.service';

@Component({
  selector: 'dynamic-form',
  templateUrl: './dynamic-form.component.html',
  providers: [QuestionControlService]
})

export class DynamicFormComponent implements OnInit {
  @Input() questions: QuestionBase<any= > [] []; form: FormGroup; payload =' ';

  constructor(
    private qcs: QuestionControlService
  ) { }

  ngOnInit() {
    console.log(this.questions);
    this.form = this.qcs.toFormGroup(this.questions);
    console.log(this.form);
  }

  onSubmit() {
    this.payload = JSON.stringify(this.form.value); }}Copy the code

You just created is a component of the entry, each question is bound to the < df – question > component element, the < df – question > tag DynamicFormQuestionComponent is matched to the components, The responsibility of this component is to dynamically render the form control based on the values of the individual questionnaire question objects.

To create its subcomponents now DynamicFormQuestionComponent

dynamic-form-question/dynamic-form.component.html

<div [formGroup] ="form">
    <div [ngSwitch] ="question.controlType">
        <input *ngSwitchCase="'input'" [formControlName] ="question.key" [id] ="question.key" [type] ="question.type">

        <select [id] ="question.key" *ngSwitchCase="'select'" [formControlName] ="question.key">
            <option *ngFor="let opt of question.options" [value] ="opt.value">{{opt.key}}</option>
          </select>
    </div>
</div>Copy the code

dynamic-form-question/dynamic-form.component.ts

import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { QuestionBase } from '.. /dynamic-form-base/question-base';

@Component({
  selector: 'df-question',
  templateUrl: './dynamic-form-question.component.html'
})

export class DynamicFormQuestionComponent {
  @Input() question: QuestionBase<any>;
  @Input() form: FormGroup;

  get isValid() { return this.form.controls[this.question.key].valid }
}Copy the code

As you can see from the above components, when you need to add a component in the future, you only need to add one type, and you can use ngSwitch to decide which type of question to display.

In both components, we rely on Angular’s formGroup to connect the template HTML to the underlying control object, which gets the rendering and validation rules from the questionnaire question model.

Note: each directory needs to export the module with index.ts. Here we need to export our registered components in theme/ Components.

Certified components

After we create the component, we need to register our component with the Module. Select Theme /nga.module.ts to inject our component.

Ng2-admin has already registered the ReactiveFormsModule for us, so we only need to register the components we created

The method is shown in the figure below

mark

Then add it to NGA_COMPONENTS

mark

Registration Service

DynamicForm expects a list of questions bound to the @Input() questions property.

QuestionService returns the list of questions defined for the job application. In a real application environment, we would get the list of questions from the database.

To maintain controls, it’s as simple as adding, updating, and deleting objects from the Questions array.

Switch to the Pages folder and start using the UserAddComponent we created in the previous chapter

pages/user/user-list/user-add/user-add.service.ts

import { Injectable } from '@angular/core';

import {
  QuestionBase,
  InputQuestion,
  SelectQuestion
} from '.. /.. /.. /.. /theme/components/dynamic-form/dynamic-form-base';

@Injectable(a)export class UserAddService {
  getQuestions() {
    let questions: QuestionBase<any> [] = [new SelectQuestion({
        key: 'brave',
        label: 'Bravery Rating',
        value: 'solid',
        options: [
          { key: 'Solid', value: 'solid' },
          { key: 'Great', value: 'great' },
          { key: 'Good', value: 'good' },
          { key: 'Unproven', value: 'unproven' }
        ],
        order: 3
      }),

      new InputQuestion({
        key: 'firstName',
        label: 'First name',
        value: 'Bombasto',
        required: true,
        order: 1
      }),

      new InputQuestion({
        key: 'emailAddress',
        label: 'Email'.type: 'email',
        order: 2})];return questions.sort((a, b) = >a.order - b.order); }}Copy the code

Need to be displayed in HTML files and component files, so modify those files

user-add.component.html

<h1>Adding user Components</h1>
<div class="user-form">
  <dynamic-form [questions] ="UserAddQuestions"></dynamic-form>
</div>Copy the code

user-add.component.ts

import { Component } from '@angular/core';

import { UserAddService } from './user-add.service';
import { QuestionBase } from '.. /.. /.. /.. /theme/components/dynamic-form/dynamic-form-base/question-base';

@Component({
  selector: 'ngt-user-add',
  templateUrl: './user-add.component.html',
  providers: [UserAddService]
})

export class UserAddComponent {
  public UserAddQuestions: QuestionBase<any= > [] [];constructor(
    private service: UserAddService
  ) {
    this.UserAddQuestions = this.service.getQuestions(); }}Copy the code

Then open your browser and type http://localhost:4200/#/pages/user/list/add

The access effect is shown below. Fill in some data and click Save. The data we need to save is displayed below

mark

The prototype of dynamic forms has been created, but there are still a few problems

  • Styles need to be optimized

  • How is data filtered and optimized

  • How data is submitted

  • Component functionality is weak

We will solve these problems in the following chapters.

Github address (branch dynamic-form) can be directly pulled down to launch the project view