FormArray и другие массивы дочернего компонента (@ViewChildren) пусты - PullRequest
0 голосов
/ 12 июля 2020

У меня есть @ViewChildren дочерний компонент (BookFormComponent) внутри родительского компонента (LibraryComponent). В моем родительском компоненте я выполняю вызов службы для получения одного объекта BookData.

Я передаю объект DookData методу дочернего компонента initBookData(...). Я хочу, чтобы дочерний компонент использовал BookData для инициализации элементов управления формой. BookData имеет атрибут selectedTypes, который содержит массив книг, которые пользователь уже выбрал. Я использую массив для проверки его флажков.

Есть 10 флажков, и, например, если у пользователя 5 элементов в массиве selectedTypes, тогда эти 5 элементов должны быть отмечены из 10 флажков при просмотре отображается.

Проблема, с которой сейчас сталкивается, заключается в том, что элементы управления формы для name и color инициализируются значениями из объекта BookData, но флажки не отмечены (выбраны), когда представление отображается. Я сделал console.log () внутри initSelectedTypes(....) дочернего компонента, и длины массивов равны 0, в то время как дочерний компонент использует те же массивы для отображения флажков в пользовательском интерфейсе, но когда он должен использовать тот же массив для установите (выберите) некоторые из флажков, тогда длины будут равны 0.

Я понимаю, что <book-form #book></book-form> в пользовательском интерфейсе родительского компонента совпадает с атрибутом @ViewChildren(BookFormComponent) book: QueryList<BookFormComponent>; в классе компонента. Итак, поскольку представление отображается, когда я вызываю метод для атрибута (книги), я ожидаю, что все атрибуты (книги) также будут инициализированы. Я не ожидаю, что массивы будут пустыми. Все флажки отображаются правильно в представлении, но когда я вызываю initBookData(...), массивы пусты.

Я использую @ViewChildren, потому что я пробовал @ViewChild, и я получал "undefined" (поэтому я не мог даже вызвать дочерние методы)

(я пропустил некоторые вещи в фрагменте кода для экономии места):

interface BookData {
   name?: string,
   color?: string,
   selectedTypes?: Array<string> // this array contains the types a user has selected already
}



// PARENT COMPONENT CLASS
class LibraryComponent implements AfterViewInit, {
    @ViewChildren(BookFormComponent) book: QueryList<BookFormComponent>;

    // ADDITIONAL CHILDREN FOR OTHER mat-step omitted for clarity

   bookData: BookData = {}

 ngAfterViewInit(): void {

   this.getBookData();

     this.book.changes.subscribe((algemen: QueryList<BookFormComponent>) => {
      book.first.initBookData(this.bookData);
    });
 }

// this method returns one book from the server and assigns it to "this.bookData"
getBookData() {
  bookdataService.getBookData().subscribe(book => {
     this.bookData = book;
});
}

}




//  PARENT COMPONENT UI
<mat-horizontal-stepper #stepper linear>

 <mat-step [stepControl]="book.bookForm">
       <ng-template matStepLabel>Book</ng-template>
       <book-form #book></book-form>
 </mat-step>

 <mat-step>
// ADDITIONAL STEPS ARE OMITTED FOR CLARITY
 </mat-step>
</mat-horizontbal-stepper>




// CHILD COMPONENT CLASS
Component({
  selector: 'book-form'
})
class BookFormComponent {

   bookForm: FormGroup;
   name = new FormControl('');
   color = new FormControl('');
   // Checkboxes for types of books a user can select. user can select multiple checkboxes
   types = new FormArray([]); 

   optionsTypes = [];

   ngOnInit(): void {
      this.bookForm = this.fb.group({
        name: this.name,
        color: this.color
       });
       
      this.initializeTypesCheckboxes();
   }

  //  This function will create 10 checkboxes that a user can select multiple of them
  private initializeTypesCheckboxes() {
    this.bookservice.getTypeOptions().subscribe(results => {
     
      // the results from the server is array of strings of 10 elements
      //  eg: ["Maths", "English", "Chemistry", ...]
      this.optionsTypes = results; 

      // we create checkboxes based on the number of types we get from the server
      const typeCheckboxes = this.optionsTypes.map(t => new FormControl(false));

     // we push the the checkboxes to the "this.types" form array
      typeCheckboxes.forEach(type => this.types.push(type));
    });
  }


// This method is called from the parent component
 public initBookData(bookData: BookData) {
   this.naam.setValue(bookData.naam);
   this.color.setValue(bookData.color);
   this.initSelectedTypes(this.types, this.optionsTypes, bookData.selectedTypes);
 }

// this method will use the already "alreadySelectedTypes" array to pre-select some of the checkboxes.
 private initSelectedTypes(formArray: FormArray, optionsTypes: Array<string>, alreadySelectedTypes: Array<string>) {
    for (let i = 0; i < formArray.controls.length; i++) {
      for (const type of alreadySelectedTypes) {
        if (optionsTypes  === type) {
          formArray.controls[i].patchValue(true);
        }
      }
    }
  console.log("LENGTH-formArray:", formArray.length); // i get O
  console.log("LENGTH-optionsTypes:", optionsTypes.length); // i get O
 }
  

}

Что я делаю не так?

Ответы [ 2 ]

0 голосов
/ 18 августа 2020

Мне непонятно, зачем ты вообще using@ViewChildren. Если я не упускаю что-то из того, что вы пытаетесь сделать, я думаю, что вы усложняете свою жизнь, чем она должна быть.

Родительский класс

Класс родительского компонента может быть сокращено до просто:

 // PARENT COMPONENT CLASS
    class LibraryComponent implements OnInit {
      // ADDITIONAL CHILDREN FOR OTHER mat-step omitted for clarity

      book: BookData = {};
      form: FormGroup = new FormGroup()

      ngOnInit(): void {
        this.bookService.getBookData.subscribe(book => (this.book = book));
      }
      //This is to get the form group from a child Output event and use it in stepper.
      onFormReady(form: FormGroup): void {
        this.form = form;
      }
    }

Родительский шаблон

Способ передачи данных дочерним компонентам от их родительского - через директиву @Input. Итак, ваш родительский шаблон будет выглядеть примерно так:
<mat-horizontal-stepper #stepper linear>

 <mat-step [stepControl]="form">
       <ng-template matStepLabel>Book</ng-template>
       <book-form (formGroup)="onFormReady($event)" [bookData]="bookData"></book-form>
 </mat-step>

 <mat-step>
// ADDITIONAL STEPS ARE OMITTED FOR CLARITY
 </mat-step>
</mat-horizontbal-stepper>

Дочерний класс

Ваш дочерний класс может позаботиться о настройке формы из своего ввода, а затем отправить группу форм обратно в родительский как @Output. Это будет выглядеть примерно так:

// CHILD COMPONENT CLASS
Component({
    selector: 'book-form'
  })
  class BookFormComponent {
    @Input('bookData') bookData: BookData
    @Output('formGroup') formEmitter = new EventEmitter<FormGroup>();
    bookForm: FormGroup;
    options: string[]
 
 ngOnInit() {
    // get and store type options at start
    this.booksService.getTypeOptions(options => {
        // once options are ready.
        // If options is empty, then the function on your service isn't working.
        this.options = options;
        this.bookForm = this.initializeForm(); // make the form
        this.formEmitter.emit(this.bookForm);  //send it to parent
    })
 }

 initializeForm(): FormGroup {
    const { name, color, selectedTypes } = this.bookData;
    const form = this.fb.group({
        name: new FormControl(name),
        color: new FormControl(color),
         types: new FormArray([])
    })
    // One form group for each possible option. Each group has a single control named after the option it represents.
    this.options.forEach(option => {
         let value = selectedTypes.includes(option);
         form.types.push(this.fb.group({[option]: new FormControl(value)})); 
    })
    return form;
  }
}

Я бы не ожидал, что это будет работать как есть, но это более или менее то направление, в котором вы должны go. Он удаляет много жира, упрощает понимание вашего кода и отправляет данные между компонентами так, как вы должны.

В документации есть очень хороший раздел, в котором рассматриваются методы для этого: https://angular.io/guide/component-interaction

0 голосов
/ 13 июля 2020

Вы пробовали ContentChildren?

@ContentChildren(BookFormComponent) book: QueryList<BookFormComponent>;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...