简体   繁体   English

子组件(@ViewChildren)的 FormArray 和其他 arrays 为空

[英]FormArray and other arrays of child Component (@ViewChildren) are empty

I have @ViewChildren child Component( BookFormComponent ) inside a parent component( LibraryComponent ).我在父组件( LibraryComponent )中有@ViewChildren子组件( BookFormComponent )。 In my parent component I make a service call to get one BookData object.在我的父组件中,我进行服务调用以获取一个BookData object。

I give the DookData object to a method of the child component initBookData(...) .我将 DookData object 提供给子组件initBookData(...)的方法。 I want the child component to use the BookData to initialize its form controls.我希望子组件使用 BookData 来初始化其表单控件。 The BookData has an attribute selectedTypes which contains an array of books the user has already selected. BookData 有一个selectedTypes属性,其中包含用户已经选择的书籍数组。 I use the array to check its checkboxes.我使用数组来检查它的复选框。

There are 10 checkboxes and for instance if a user has 5 elements in the selectedTypes array then those 5 elements has to be checked out of the 10 checkboxes when the view is displayed.有 10 个复选框,例如,如果用户在selectedTypes数组中有 5 个元素,则在显示视图时必须将这 5 个元素从 10 个复选框中选中。

The issue am having now is the form controls for name and color are initialized with the values from the BookData object but the checkboxes are not checked(selected) when the view is displayed.现在遇到的问题是namecolor的表单控件是使用BookData object 中的值初始化的,但是在显示视图时未选中(选中)复选框。 I did console.log()'s inside initSelectedTypes(....) of the child component and the lengths of the arrays are 0's meanwhile the child component uses the same arrays to display the checkboxes in the UI but when it has to use the same array to check(select) some of the checkboxes then the lengths are 0's.我在子组件的initSelectedTypes(....)中做了 console.log(),arrays 的长度为 0,同时子组件使用相同的 arrays 在 UI 中显示复选框,但是当它必须使用时相同的数组来检查(选择)一些复选框,然后长度为 0。

My understanding is that the <book-form #book></book-form> in the parent component UI is the same as the attribute @ViewChildren(BookFormComponent) book: QueryList<BookFormComponent>;我的理解是父组件UI中的<book-form #book></book-form>和属性@ViewChildren(BookFormComponent) book: QueryList<BookFormComponent>; in the component class.在组件 class 中。 So since the view is displayed then when I call a method on the attribute (book) then I expect all attributes of (book) to be initialized as well.因此,由于显示了视图,所以当我在属性(书)上调用方法时,我希望(书)的所有属性也被初始化。 I don't expect the arrays to be empty.我不希望 arrays 为空。 All checkboxes are displayed correctly in the view but when I call initBookData(...) the arrays are empty.所有复选框都在视图中正确显示,但是当我调用initBookData(...)时 arrays 为空。

I am using @ViewChildren because I tried @ViewChild and I was getting "undefined" (so could not even call the child's methods)我正在使用@ViewChildren,因为我尝试了@ViewChild,但我得到了“未定义”(所以甚至不能调用孩子的方法)

(I have omitted certain things in the code snippet to conserve space): (我在代码片段中省略了某些内容以节省空间):

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
 }
  

}

What am I doing wrong?我究竟做错了什么?

Have you tried ContentChildren?您是否尝试过 ContentChildren?

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

It's unclear to me why you're using@ViewChildren at all.我不清楚你为什么要使用@ViewChildren。 Unless I'm missing something about what you're trying to do, I think you're making your life more complicated than it needs to be.除非我错过了你想要做的事情,否则我认为你让你的生活变得比它需要的更复杂。

The Parent Class父 Class

Your parent component class can be stripped down to just:您的父组件 class 可以简化为:

 // 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;
      }
    }

Parent template父模板

The way to pass data to child components from their parent is through an @Input directive. 将数据从父组件传递到子组件的方法是通过 @Input 指令。 So, your parent template would look something like: 因此,您的父模板看起来像:
 <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>

Child Class儿童 Class

Your child class can take care of setting up the form from its input, and then send the form group back up to the parent as a @Output.您的孩子 class 可以负责从其输入设置表单,然后将表单组作为@Output 发送回父级。 It would look something like this:它看起来像这样:

 // 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; } }

I wouldn't expect this to work as is, but it's more or less the direction you should go in. It strips out a lot of the fat, makes your code easier to understand, and sends data between components the way you're supposed to.我不希望它按原样工作,但它或多或少是您应该使用 go 的方向。它去除了很多脂肪,使您的代码更易于理解,并以您应该的方式在组件之间发送数据至。

The docs have a very good section that goes over the methods for doing this: https://angular.io/guide/component-interaction文档有一个非常好的部分介绍了执行此操作的方法: https://angular.io/guide/component-interaction

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM