【问题标题】:FormArray and other arrays of child Component (@ViewChildren) are emptyFormArray 和其他子组件数组(@ViewChildren)为空
【发布时间】:2020-08-18 09:07:42
【问题描述】:

我在父组件(LibraryComponent) 中有@ViewChildren 子组件(BookFormComponent)。在我的父组件中,我进行服务调用以获取一个 BookData 对象。

我将 DookData 对象赋予子组件initBookData(...) 的一个方法。我希望子组件使用 BookData 来初始化其表单控件。 BookData 有一个属性selectedTypes,其中包含用户已经选择的书籍数组。我使用数组来检查它的复选框。

有 10 个复选框,例如,如果用户在 selectedTypes 数组中有 5 个元素,则在显示视图时必须从 10 个复选框中选中这 5 个元素。

现在遇到的问题是namecolor 的表单控件使用来自BookData 对象的值进行初始化,但在显示视图时未选中(选中)复选框。我在子组件的initSelectedTypes(....) 内做了console.log(),数组的长度为0,同时子组件使用相同的数组来显示UI中的复选框,但是当它必须使用相同的数组来检查(选择)一些复选框,然后长度为 0。

我的理解是父组件UI中的<book-form #book></book-form>和组件类中的属性@ViewChildren(BookFormComponent) book: QueryList<BookFormComponent>;是一样的。因此,由于显示了视图,因此当我在属性(书)上调用方法时,我希望(书)的所有属性也被初始化。我不希望数组为空。所有复选框都在视图中正确显示,但是当我调用 initBookData(...) 时,数组为空。

我正在使用@ViewChildren,因为我尝试了@ViewChild,但我得到了“未定义”(所以甚至不能调用孩子的方法)

(为了节省空间,我在代码 sn-p 中省略了某些内容):

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
 }
  

}

我做错了什么?

【问题讨论】:

    标签: javascript html angular typescript


    【解决方案1】:

    您尝试过 ContentChildren 吗?

    @ContentChildren(BookFormComponent) book: QueryList&lt;BookFormComponent&gt;;

    【讨论】:

      【解决方案2】:

      我不清楚你为什么要使用@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;
        }
      }
      

      我不希望它按原样工作,但这或多或少是您应该采取的方向。它去除了很多脂肪,使您的代码更易于理解,并以您的方式在组件之间发送数据'应该。

      文档中有一个非常好的部分介绍了执行此操作的方法: https://angular.io/guide/component-interaction

      【讨论】:

        猜你喜欢
        • 2017-08-31
        • 2019-03-22
        • 1970-01-01
        • 2023-01-12
        • 2018-11-28
        • 2017-12-25
        • 2017-11-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多