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



我在父组件(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>;相同。因此,由于视图是显示的,所以当我调用属性(book(上的方法时,我希望(book(的所有属性也被初始化。我不希望数组为空。所有复选框都正确显示在视图中,但当我调用initBookData(...)时,数组为空。

我之所以使用@ViewChildren,是因为我尝试了@ViewChild;未定义的";(所以甚至不能调用孩子的方法(

(为了节省空间,我在代码片段中省略了某些内容(:

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
}

}

我做错了什么?

您尝试过ContentChildren吗?

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

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

我不希望这样做,但这或多或少是你应该走的方向。它去除了很多脂肪,使你的代码更容易理解,并以你应该的方式在组件之间发送数据。

文档中有一个非常好的部分介绍了实现这一点的方法:https://angular.io/guide/component-interaction

最新更新