我正在关注this tutorial以角度创建动态表单。 full code for the tutorial is on github一切都运行良好,但我想添加一些内容,其中使用Directive
和ComponentFactoryResolver
来动态创建子组件的父组件能够访问子组件
这是指令代码:
import { ComponentFactoryResolver, ComponentRef, Directive, Input, OnChanges, OnInit, Type, ViewContainerRef } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormButtonComponent } from '../form-button/form-button.component';
import { FormInputComponent } from '../form-input/form-input.component';
import { FormSelectComponent } from '../form-select/form-select.component';
import { Field } from '../../models/field.interface';
import { FieldConfig } from '../../models/field-config.interface';
const components: {[type: string]: Type<Field>} = {
button: FormButtonComponent,
input: FormInputComponent,
select: FormSelectComponent
};
@Directive({
selector: '[dynamicField]'
})
export class DynamicFieldDirective implements Field, OnChanges, OnInit {
@Input()
config: FieldConfig;
@Input()
group: FormGroup;
component: ComponentRef<Field>;
constructor(
private resolver: ComponentFactoryResolver,
private container: ViewContainerRef
) {}
ngOnChanges() {
if (this.component) {
this.component.instance.config = this.config;
this.component.instance.group = this.group;
}
}
ngOnInit() {
if (!components[this.config.type]) {
const supportedTypes = Object.keys(components).join(', ');
throw new Error(
`Trying to use an unsupported type (${this.config.type}).
Supported types: ${supportedTypes}`
);
}
const component = this.resolver.resolveComponentFactory<Field>(components[this.config.type]);
this.component = this.container.createComponent(component);
this.component.instance.config = this.config;
this.component.instance.group = this.group;
//let parentCmp = ......;
//parentCmp.addField(this.component.instance);
}
}
由于Directive
是创建子组件的位置,因此我认为这是我可以访问父组件和子组件的地方,并且可以将子组件传递给父组件。这就是我试图用这两个评论做的
ngOnInit中的行:
let parentCmp = ......;
parentCmp.addField(this.component.instance);
这是使用该指令的组件。
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { FieldConfig } from '../../models/field-config.interface';
@Component({
exportAs: 'dynamicForm',
selector: 'dynamic-form',
styleUrls: ['dynamic-form.component.scss'],
template: `
<form
class="dynamic-form"
[formGroup]="form"
(submit)="handleSubmit($event)">
<ng-container
*ngFor="let field of config;"
dynamicField
[config]="field"
[group]="form">
</ng-container>
</form>
`
})
export class DynamicFormComponent implements OnChanges, OnInit {
@Input()
config: FieldConfig[] = [];
fields: object;
@Output()
submit: EventEmitter<any> = new EventEmitter<any>();
form: FormGroup;
get controls() { return this.config.filter(({type}) => type !== 'button'); }
get changes() { return this.form.valueChanges; }
get valid() { return this.form.valid; }
get value() { return this.form.value; }
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.form = this.createGroup();
}
ngOnChanges() {
if (this.form) {
const controls = Object.keys(this.form.controls);
const configControls = this.controls.map((item) => item.name);
controls
.filter((control) => !configControls.includes(control))
.forEach((control) => this.form.removeControl(control));
configControls
.filter((control) => !controls.includes(control))
.forEach((name) => {
const config = this.config.find((control) => control.name === name);
this.form.addControl(name, this.createControl(config));
});
}
}
addField(field){
this.fields[field.key] = field;
}
createGroup() {
const group = this.fb.group({});
this.controls.forEach(control => group.addControl(control.name, this.createControl(control)));
return group;
}
createControl(config: FieldConfig) {
const { disabled, validation, value } = config;
return this.fb.control({ disabled, value }, validation);
}
handleSubmit(event: Event) {
event.preventDefault();
event.stopPropagation();
this.submit.emit(this.value);
}
setDisabled(name: string, disable: boolean) {
if (this.form.controls[name]) {
const method = disable ? 'disable': 'enable';
this.form.controls[name][method]();
return;
}
this.config = this.config.map((item) => {
if (item.name === name) {
item.disabled = disable;
}
return item;
});
}
setValue(name: string, value: any) {
this.form.controls[name].setValue(value, {emitEvent: true});
}
}
答案 0 :(得分:0)
解决方案是在指令中使用@Output。使用Directive中的输出,您可以创建父组件可以订阅的偶数发射器。这样,该指令可以将数据传递回父组件。在我的例子中,我在指令中创建了一个输出,它将子组件传递给它:
import { ComponentFactoryResolver, EventEmitter, ComponentRef, Directive, Input, OnChanges, OnInit, Type, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[dynamicField]'
})
export class DynamicFieldDirective implements Field, OnChanges, OnInit {
@Input()
config: FieldConfig;
@Input()
group: FormGroup;
@Output fieldAdded: EventEmitter<any> = new EventEmitter<any>();
component: ComponentRef<Field>;
constructor(
private resolver: ComponentFactoryResolver,
private container: ViewContainerRef
) {}
ngOnChanges() {
if (this.component) {
this.component.instance.config = this.config;
this.component.instance.group = this.group;
}
}
ngOnInit() {
if (!components[this.config.type]) {
const supportedTypes = Object.keys(components).join(', ');
throw new Error(
`Trying to use an unsupported type (${this.config.type}).
Supported types: ${supportedTypes}`
);
}
const component = this.resolver.resolveComponentFactory<Field>(components[this.config.type]);
this.component = this.container.createComponent(component);
this.component.instance.config = this.config;
this.component.instance.group = this.group;
this.fieldAdded.emit(this.component.instance);
}
}
在父组件中,我可以将一个函数绑定到指令的输出事件,该指令然后处理传递给它的子组件,如下所示:
@Component({
exportAs: 'dynamicForm',
selector: 'dynamic-form',
styleUrls: ['dynamic-form.component.scss'],
template: `
<form
class="dynamic-form"
[formGroup]="form"
(submit)="handleSubmit($event)">
<ng-container
*ngFor="let field of config;"
dynamicField
[config]="field"
[group]="form"
(fieldAdded) = "addField($event)"
>
</ng-container>
</form>
`
})
export class DynamicFormComponent implements OnChanges, OnInit {
@Input()
config: FieldConfig[] = [];
fields: object = {};
addField(field: any){
this.fields[field.key] = field;
}
此时,父组件有一个this.fields属性,该属性包含所有子组件实例,并且可以使用它们。