즉석에서 동적 Angular 양식 빌드

양식은 Angular 프로젝트에서 다루기에 결코 간단한 일이 아닙니다. 양식은 마크업뿐만 아니라 FormControls가 있는 구성 요소에서도 각각을 "적절하게"디자인하고 모든 것이 서로 잘 맞는지 확인해야 합니다.
또한 빠르게 변화하는 비즈니스 및 규정 요구 사항을 충족하기 위해 자주 변경될 수 있음을 염두에 두어야 합니다.

비즈니스 개체 모델을 설명하는 메타데이터를 사용하여 즉석 양식을 만드는 방법을 살펴보겠습니다.

메타데이터



메타데이터는 시스템에 다음 내용을 표시합니다.
  • 필드 이름
  • 필드형
  • 검증 조건
  • 자리 표시자, 패턴 등과 같은 기타 항목...

  • JSON으로 구조화되지만 원하는 언어(JSON+LD, csv, XML 또는 원하는 형식)를 분명히 사용할 수 있습니다.

    데이터 소스는 API, 파일 또는 기타 사용 가능한 데이터 소스일 수도 있습니다.

    JSON에서는 다음과 같이 표시됩니다(필요에 맞게 조정할 수 있음).

    // question-base.ts
    
    export class QuestionBase<T> {
      value: T;
      key: string;
      label: string;
      required: boolean;
      order: number;
      controlType: string;
      placeholder: string;
      iterable: boolean;
    
      ...
    }
    
    

    이것은 다음과 같이 생성할 다른 모든 종류의 요소에 대한 골격이 됩니다.
  • 입력
  • 텍스트 영역
  • 선택
  • 기타 양식 필드...

  • 이러한 각 양식 요소는 동일한 항목Class을 공유하고 적절한 필요에 맞게 확장합니다. 예를 들어 option<select> 요소에만 유용합니다.

    // question-dropdown.ts
    
    import { QuestionBase } from './question-base';
    
    export class DropdownQuestion extends QuestionBase<string> {
      controlType = 'dropdown';
      options: { key: string, value: string }[] = [];
    
      constructor(options: {} = {}) {
        super(options);
        this.options = options['options'] || [];
      }
    }
    

    구성 요소



    코드를 유연하고 안정적이며 쉽게 테스트하고 유지 관리할 수 있도록 하기 위해 두 부분으로 나뉩니다. 첫째, 항상 앱의 구성 요소에서 래퍼로 호출되는 구성 요소( app-dynamic-form )가 있습니다.

    <!-- app.component.html -->
    
    <app-dynamic-form #dynamicForm
                      [questions]="questions"></app-dynamic-form>
    

    그런 다음 각각의 별도 양식 필드를 만들기 위해 app-question에 의해 호출되고 반복되는 app-dynamic-form 구성 요소:

    <!-- dynamic-form.component.html -->
    
    ...
    <div *ngFor="let question of questions"
         class="form-row">
      <app-question [question]="question"
                    [form]="form"></app-question>
    </div>
    ...
    

    iterable(반복 가능)하게 만들기



    위에서 볼 수 있듯이 app-questionngFor 의 컬렉션을 순환하는 questions 안에 래핑되어 있습니다.

    이 구성 요소 안에는 QuestionBase 가 있습니다. 그것의 임무는 객체에 주어진 필드 유형에 따라 올바른 HTMLElement를 표시하는 것입니다.

    <!-- dynamic-form-question.component.html -->
    
    <div [ngSwitch]="question.controlType">
    
        <input *ngSwitchCase="'textbox'"
                [formControl]="questionControl(index)"
                [placeholder]="question.placeholder"
                [attr.min]="question['min']"
                [attr.max]="question['max']"
                [attr.pattern]="question['pattern']"
                [id]="questionId(index)"
                [type]="question['type']">
    
        <select [id]="question.key"
                *ngSwitchCase="'dropdown'"
                [formControl]="questionControl(index)">
        <option value=""
                disabled
                *ngIf="!!question.placeholder"
                selected>{{ question.placeholder }}</option>
        <option *ngFor="let opt of question['options']"
                [value]="opt.key">{{ opt.value }}</option>
        </select>
    
        ...
    
    </div>
    
    ngSwitch에 할당된 [attr.min]="question['min']" 속성이 있는 요소에 options와 같은 속성 값을 전달하는 방식을 눈치챘을 것입니다.

    // question-dropdown.ts
    
    import { QuestionBase } from './question-base';
    
    export class TextboxQuestion extends QuestionBase<string> {
      type: string;
      min: number | string;
      ...
    
      constructor(options: {} = {}) {
        super(options);
        this.type = options['type'] || 'text';
        this.min = options['min'];
        ...
    }
    

    하지만 constructor 표시할 뿐만 아니라 FormControl 도 좋습니다! 이제 몇 가지 콘텐츠 프로젝션을 살펴보겠습니다.

    <!-- dynamic-form-question.component.html -->
    
    <div *ngIf="question.iterable; else formTmpl">
        <div *ngFor="let field of questionArray.controls; 
                     let i=index; first as isFirst last as isLast">
    
            <ng-container [ngTemplateOutlet]="formTmpl"
                        [ngTemplateOutletContext]="{index: i}"></ng-container>
    
            <button *ngIf="question.iterable && questionArray.controls.length > 1"
                    (click)="removeQuestion(i)"
                    type="button">-</button>
    
            <button *ngIf="question.iterable && isLast"
                    (click)="addQuestion()"
                    type="button">+</button>
    
        </div>
    </div>
    

    이 라인 FormArray<div *ngIf="question.iterable; else formTmpl">의 컬렉션 또는 간단한 FormArray를 표시하기로 결정하여 FormControl로 래핑된 라인임을 알 수 있습니다. 이것이 우리가 어떤 반복 단계에 있는지 알 수 있는 유일한 방법이라는 점을 감안하여 현재 인덱스를 ng-template로 전달하고 있습니다.

    <!-- dynamic-form-question.component.html -->
      ..
      <ng-template #formTmpl
                   let-index="index">
        <label [attr.for]="questionId(index)">{{ questionLabel(index) }}</label>
    
        <div [ngSwitch]="question.controlType">
        ...
    

    여기서 문제는 올바른 let-index="index" 요소(우리가 반복하고 있는 요소)와의 "링크"를 유지하는 것입니다. 왜냐하면 이 구성을 사용하면 questionquestions가 있기 때문입니다. a question 가 반복 가능한지 확인하는 유일한 방법은 questioniterable 속성을 확인하는 것이기 때문에 유형과 클래스는 이 시점에서 동일하게 유지됩니다.
    question로 주입된 index 속성 덕분에 다음을 사용하여 <ng-template #formTmpl let-index="index">에서 쉽게 검색할 수 있습니다.

    <ng-container [ngTemplateOutlet]="formTmpl"
                  [ngTemplateOutletContext]="{index: i}"></ng-container>
    

    컬렉션의 올바른 반복 작업을 수행합니다.

    데모 및 코드



    모든 소스 코드는 Github에서 사용할 수 있으며 동적 양식의 놀라운 기능을 보고 싶다면 이미 데모를 사용할 수 있습니다!




    최대화 / 각도 동적 형태


    Angular를 사용하여 데이터에서 즉각적인 양식 생성






    🔥 Demo available here 🔥

    학점

    사진 제공: Patrick Langwallner on Unsplash
    다시 읽고 수정해 주셔서 감사합니다.

    좋은 웹페이지 즐겨찾기