Значение исправления для массива вложенных и динамических форм angular

#angular #angular-reactive-forms #angular-forms

#angular #угловые-реактивные-формы #angular-формы

Вопрос:

на самом деле я не знаю, как называется эта проблема, я создаю конструктор форм, подобный Google form, на основе этого кодаhttps://stackblitz.com/edit/angular-dynamic-survey-creation-golkhg , я создал форму викторины и успешно добавил ее в базу данных, но теперь я хочу создать страницу «редактировать форму викторины».

вот мой полный код

 import { Component, OnInit } from '@angular/core';
import { FormArray, FormGroup, FormControl, Validators } from '@angular/forms';
import { UserService } from '../../../../../auth/user.service';
import { NbToastrService, NbComponentStatus } from '@nebular/theme';
import { Router } from '@angular/router';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';

@Component({
  selector: 'quiz-edit',
  templateUrl: './quiz-edit.component.html',
  styleUrls: ['./quiz-edit.component.scss']
})
export class QuizEditComponent implements OnInit {
  editQuizForm: FormGroup;
  selectedPanduan = [];
  selectedOption = [];
  list: any;
  href: string;
  lessonSlug: string;
  topicSlug: string;
  materiSlug: string;
  quizData: any;
  constructor(
    private userService : UserService,
    private toastrService: NbToastrService,
    private router : Router
  ) { 
    this.materiSlug = localStorage.getItem('materiSlug');
    this.topicSlug = localStorage.getItem('topicSlug');
    this.lessonSlug = localStorage.getItem('lessonSlug');
  }

  ngOnInit() {
    this.initForm();   
    this.getQuizDetail();
    this.href = localStorage.getItem('currentMateri'); 
  }
  private initForm() {
    let name = '';
    let description = '';
    let questions = new FormArray([]);
    this.editQuizForm = new FormGroup({
      'name': new FormControl(name, [Validators.required]),
      'description': new FormControl(description, [Validators.required]),
      'materialTopicSlug': new FormControl(localStorage.getItem('topicSlug'), [Validators.required]),
      'questions': questions,
    });
    this.onAddCard();
  }
  get questionFormArray() {
    return this.editQuizForm.get('questions') as FormArray;
  }
  getQuizDetail() {
    this.userService.getAdminQuizDetail(this.lessonSlug).subscribe(
      (data: any) => {
        console.log(data);
        this.quizData = data.data;
        this.editQuizForm.patchValue({
          name : this.quizData.name,
          description : this.quizData.description,
          questions : this.quizData.questions,
        });
      },
      (error: any) => {
        console.log(error);
      }
    )
  }
  //Ubah Order dengan Drag
  drop(event: CdkDragDrop<string[]>) {
    this.list = this.editQuizForm.get("questions")["controls"];
    console.log(this.list);
    moveItemInArray(this.list, event.previousIndex, event.currentIndex);
    this.questionFormArray.controls[event.currentIndex]['controls']['display_order']
      .setValue(event.currentIndex   1);
    this.questionFormArray.controls.forEach((category, index) => {
      (category as FormGroup).controls['display_order'].setValue(index   1);
    });
    moveItemInArray(this.editQuizForm.get('questions')['controls'], event.previousIndex, event.currentIndex);
    this.editQuizForm.get('questions').updateValueAndValidity({ onlySelf: false });
  }
  showCreateQuizToast(message, status: NbComponentStatus) {
    this.toastrService.show(status, `${message}`, { status });
  }
  //Fungsi Tambah Pertanyaan
  onAddCard() {
    console.log(this.editQuizForm.controls);
    const quizQuestionItem = new FormGroup({
      // questionType: new FormControl("", Validators.required),
      display_order: new FormControl(this.questionFormArray.length   1),
      questionTitle: new FormControl("", Validators.required),
      options: new FormArray([])
      // questionGroup: new FormGroup({
      // })
    });
    (<FormArray>this.editQuizForm.get('questions')).push(quizQuestionItem);
  }
  onRemoveCard(index) {
    this.editQuizForm.controls.questions['controls'][index].controls.questionGroup = new FormGroup({});
    this.editQuizForm.controls.questions['controls'][index].controls.questionType = new FormControl({});
    (<FormArray>this.editQuizForm.get('questions')).removeAt(index);
    this.selectedOption.splice(index, 1)
    console.log(this.editQuizForm);
  }
  //Fungsi Tambah Opsi untuk Kotak Centang dan Radio
  addOptionControls(questionType, index) {
    let options = new FormArray([]);
    (this.editQuizForm.controls.questions['controls'][index]).addControl('options', options);
    this.clearFormArray((<FormArray>this.editQuizForm.controls.questions['controls'][index].controls.options));
    this.addOption(index);
    this.addOption(index);
  }
  private clearFormArray(formArray: FormArray) {
    while (formArray.length !== 0) {
      formArray.removeAt(0)
    }
  }
  checked = false;
  toggle(checked: boolean) {
    return this.checked = checked;
  }
  toggleAnswer(checked: boolean) {
    console.log(checked);
    
  }
  addOption(index) {
    const optionGroup = new FormGroup({
      'optionText': new FormControl('', Validators.required),
      'isAnswer': new FormControl(this.toggle(this.checked), Validators.required),
    });
    (<FormArray>this.editQuizForm.controls.questions['controls'][index].controls.options).push(optionGroup);
  }

  removeOption(questionIndex, itemIndex) {
    (<FormArray>this.editQuizForm.controls.questions['controls'][questionIndex].controls.options).removeAt(itemIndex);
  }
  //Fungsi Simpan Form Dalam JSON
  postQuiz() {
    let formData = this.editQuizForm.value;
    console.log(formData);
    this.userService.addAdminMateriTopicQuiz(formData).subscribe(
      (data:any)=>{
        console.log(data);
        this.showCreateQuizToast(data.message[0].message, 'success');
        this.router.navigateByUrl(this.href);
      },
      error=>console.log(error)
    )
    // let editQuizFormData: any;
    // editQuizFormData = {
    //   name: this.editQuizForm.get('name').value,
    //   description: this.editQuizForm.get('description').value,
    //   materialTopicSlug : this.editQuizForm.get('description').value,
    //   questions : this.editQuizForm.get('questions').value,
    // }
    // console.log(editQuizFormData);

    // console.log();
    // let ID = 0;
    // let Desc = formData.description;
    // let Name = formData.name;
    // let quizID = formData.nomorquiz;
    // let jenisquiz = formData.selectedPanduan;
    // let IsDeleted = false; 
    // let Question: Question[] = [];
    // let Questions = [];
    // let questions = formData.questions;
    // let optionArray = formData.questions[0].questionGroup.options[0].optionText
    // let survey = new Survey(ID, Desc, Name, IsDeleted, quizID, jenisquiz, Questions);
    // questions.forEach((question, index, array) => {
    //   let questionItem = {
    //     'ID': 0,
    //     "Type": question.questionType,
    //     "Text": question.questionTitle,
    //     "options": [],
    //     "Required": false,
    //   }
    //   if (question.questionGroup.hasOwnProperty('options')) {
    //     question.questionGroup.options.forEach(option => {
    //       let optionItem: Option = {
    //         "ID": 0,
    //         "OptionText": option.optionText,
    //         "OptionColor": ""
    //       }
    //       questionItem.options.push(optionItem)
    //     });
    //   }

    //   survey.Question.push(questionItem)

    // });
    // console.log(survey);
    console.log('posting survey');
  }
  onSubmit() {
    this.postQuiz();
  }
}  
 @import "../../../../../@theme/styles/themes";

@include nb-install-component() {
    .buttons-row {
        margin: -0.5rem;
    }

    button[nbButton] {
        margin: 0.5rem;
    }

    .action-icon {
        @include nb-ltr(margin-right, 0.5rem);
        @include nb-rtl(margin-left, 0.5rem);
    }

    .actions-card {
        height: 8rem;
    }
}

.nb-theme-default [nbButton].appearance-filled.size-medium {
    margin: 0px;
}

.myIframe {
    position: relative;
    padding-bottom: 50.25%;
    padding-top: 30px;
    height: 0;
    overflow: auto;
    -webkit-overflow-scrolling: touch; //<<--- THIS IS THE KEY
}
.myIframe iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.stepContainer {
    width: 800px;
    margin: auto;
}
.stepEmph {
    font-size: x-large;
    line-height: 2rem;
}
.divider {
    height: 5px;
    width: 60px;
    background-color: #3366ff;
    border-radius: 5px;
    margin: auto;
}
@media screen and (max-width: 900px) {
    .stepContainer {
        width: 100%;
        margin: auto;
    }
    .stepEmph {
        font-size: larger;
        line-height: 1.7rem;
    }
}
@media screen and (max-width: 425px) {
    .stepEmph {
        font-size: medium;
        line-height: 1.5rem;
    }
}

.form-group {
    width: 100%;
    margin: 10px auto;
    padding: 0px 15px;
}

.myIframe {
    position: relative;
    padding-bottom: 50.25%;
    padding-top: 30px;
    height: 0;
    overflow: auto;
    -webkit-overflow-scrolling: touch; //<<--- THIS IS THE KEY
}
.myIframe iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.floatingButtonContainer {
    position: fixed;
    bottom: 1rem;
    right: 1.5rem;
    width: 4.4em;
    z-index: 10;
}
.floatingButton {
    border-radius: 50% !important;
    padding: 1.3rem !important;
    z-index: 1000;
    transition: all 0.5s ease;
    transform: translateY(0px);
    opacity: 1;
}
.floatingButtonMenu {
    border-radius: 50% !important;
    padding: 0.9rem !important;
    z-index: 1000;
    transition: all 0.5s cubic-bezier(0.01, 0.62, 0.32, 0.97);
    transform: translateY(0px);
    margin: 5px !important;
}
.fabMenuShow {
    display: block;
    list-style: none;
    opacity: 1;
    transform: translateY(0px);
    transition: all 0.5s cubic-bezier(0.01, 0.62, 0.32, 0.97);
}
.fabMenuHide {
    display: block;
    list-style: none;
    opacity: 0;
    transform: translateY(10px);
    transition: all 0.5s cubic-bezier(0.01, 0.62, 0.32, 0.97);
}
.fabMenuShow li,
.fabMenuHide li {
    float: right;
    margin-bottom: 5px;
}

.cdk-drag-placeholder {
    opacity: 0;
}

.cdk-drag-animating {
    transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.formList:last-child {
    border: none;
}

.formListsContainer.cdk-drop-list-dragging .formList:not(.cdk-drag-placeholder) {
    transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.cdk-drag-preview {
    // box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
    box-shadow: 0 0.5rem 1rem 0 rgba(44, 51, 73, 0.2);
    list-style: none;
}  
 <div class="floatingButtonContainer">
    <button nbButton (click)="onAddCard()" class="floatingButton" nbTooltip="Tambah Form" nbTooltipStatus="primary"
        nbTooltipPlacement="left">
        <nb-icon icon="plus-outline"></nb-icon>
    </button>
</div>
<form [formGroup]="editQuizForm" (ngSubmit)="onSubmit()" autocomplete="off" class="row">
    <nb-card class="col-lg-5" size="small" style="height: 250px;">
        <div class="form-group">
            <label for="exampleInputEmail1" class="label">Nama Quiz</label>
            <input type="text" formControlName="name" nbInput fullWidth fieldSize="medium" placeholder="Nama Quiz">
        </div>
        <div class="form-group">
            <label for="exampleInputEmail1" class="label">Nama Quiz</label>
            <textarea formControlName="description" nbInput fullWidth placeholder="Deskripsi Quiz"></textarea>
        </div>
    </nb-card>
    <div formArrayName="questions" class="poll-options" class="col-lg-7">
        <ol style="list-style: none;padding: 0;" cdkDropList (cdkDropListDropped)="drop($event)"
            class="formListsContainer">
            <li *ngFor="let questionCtrl of editQuizForm.get('questions')['controls']; let i = index" cdkDrag
                cdkDragLockAxis="y" class="formList">
                <nb-card style="position: relative;" [formGroupName]="i">
                    <nb-card-header cdkDragHandle
                        style="text-align: center;padding: 0px;cursor: pointer;border-bottom: 0;">
                        <nb-icon icon="more-horizontal-outline"></nb-icon>
                    </nb-card-header>
                    <button *ngIf="i>=0" (click)="onRemoveCard(i)" status="danger"
                        style="padding:0px;border-radius:50%;position: absolute;top: -20px;right: -20px;width: 40px;height: 40px;"
                        nbButton>
                        <nb-icon icon="close-outline"></nb-icon>
                    </button>
                    <nb-card-body>
                        <div class="form-group row" style="padding: 0px;">
                            <div class="col-12" style="padding: 0px;">
                                <div>
                                    <div *ngIf="questionCtrl.controls.options">
                                        <div class="col-12" style="padding:0px;margin:5px auto;">
                                            <textarea placeholder="Pertanyaan" formControlName="questionTitle" nbInput
                                                fullWidth></textarea>
                                        </div>
                                        <ul style="padding: 0;" formArrayName="options">
                                            <li style="list-style: none;"
                                                *ngFor="let optionCtrl of questionCtrl.controls.options.controls let j = index">
                                                <div [formGroupName]="j">
                                                    <button style="margin: 0px 5px;" nbButton *ngIf="j>=0"
                                                        (click)="removeOption(i,j)" status="danger">X
                                                    </button>
                                                    <input style="margin: 5px auto;max-width: 60%;width: 50%;" nbInput
                                                        formControlName="optionText" placeholder="option text"
                                                        maxlength="100" [required]="true">
                                                    <nb-checkbox formControlName="isAnswer">Jawaban</nb-checkbox>
                                                </div>
                                            </li>
                                        </ul>
                                        <button nbButton status="primary" type="button" (click)="addOption(i)"
                                            style="margin-top:5px;" color="accent">
                                            <nb-icon icon="plus-outline"></nb-icon> Add option
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </nb-card-body>
                </nb-card>
            </li>
        </ol>
    </div>
</form>
<button nbButton fulWidth status="success" (click)="postQuiz()">Simpan Form</button>  

Ответ quizData

    {
  "name": "quiz 2",
  "description": "desk quiz 2",
  "questions": [
    {
      "code": "Yb58vyqyoOcGu7k",
      "name": "tanya 1",
      "order": null,
      "options": [
        {
          "code": "xqANiy2YY5hBp0f",
          "name": "test",
          "order": 1
        }
      ]
    },
    {
      "code": "XEj18dHM2UEd7um",
      "name": "tanya 2",
      "order": null,
      "options": [
        {
          "code": "XDcGJ3ujHZYyrf2",
          "name": "test 2",
          "order": 1
        }
      ]
    }
  ]
}
  

введите описание изображения здесь

о, справа должен быть массив форм, questions и options из ответа должно быть в форме, потому что я добавил patchValue в editQuizForm . но это не работает, кто-нибудь знает как?

вот как это должно выглядеть

введите описание изображения здесь

Комментарии:

1. Было бы лучше, если бы вы включили свой пример в stackblitz

2. да, но я не могу воспроизвести это с помощью nebular в stackblitz, я не знаю почему, поэтому я просто привожу вам пример stacblitz, надеюсь, что кто-нибудь сможет понять только с этим

3. на самом деле это почти то же самое

4. вот простое создание и редактирование stackblitz.com/edit /…

5. @noval — вы смогли найти решение? Если да, можете ли вы опубликовать это в качестве ответа здесь

Ответ №1:

Поэтому вместо использования patchValue , которое доставляло мне головную боль, я добавляю значение из API к каждому элементу управления формой следующим образом

 let itemControl = this.fb.group({
  name: [
    sectionItems ? sectionItems.name : "",
    Validators.compose([Validators.required]),
  ],
  code: [
    sectionItems ? sectionItems.code : "",
    Validators.compose([Validators.required]),
  ],
  questionType: [
    sectionItems ? sectionItems.type : "",
    Validators.compose([Validators.required]),
  ],
  url: [
    sectionItems ? sectionItems.url : "",
    Validators.compose([Validators.required]),
  ],
  filePath: [
    sectionItems ? sectionItems.filePath : "",
    Validators.compose([Validators.required]),
  ],
  materialSlug: [
    sectionItems amp;amp; sectionItems.material != null
      ? sectionItems.material.slug
      : "",
    Validators.compose([Validators.required]),
  ],
  description: [
    sectionItems ? sectionItems.description : "",
    Validators.compose([Validators.required]),
  ],
  options: this.fb.array([]),
  display_order: new FormControl(max   1),
});
  

Проверьте, есть ли у элемента управления существующее значение, если нет, установите для него значение empty ""

чтобы отобразить все данные в виде массива форм, я сделал что-то вроде этого

 (<FormArray>this.editSectionForm.get("formItems")).push(itemControl);
  

Таким образом, я смог отобразить все мои данные на карточках с их типом формы на основе данных JSON, а также пользователь может изменять, добавлять и удалять форму