Angular 5 动态添加到表单的组件胶合逻辑
Angular 5 glue logic of components dynamically added to form
我正在使用 Angular 5 并且我需要创建一个组件 (dynform
) 来实例化一组自定义组件 (dyncompA
, dyncompB
, 等等)。哪些是在 dynform.ngOnInit
决定的,因此它们不会在父模板中声明,而是动态添加。每个子组件都有一个 MyObjectA
、MyObjectB
等类型的值,派生自 MyObjectAbstract
(不是字符串),我为其实现了一个 ControlValueAccessor
接口。
我遇到的问题是,父表单从未收到有关子组件的有效性状态或其已更改(!原始)状态的通知。我的自定义验证器也从未被调用过。此外,子组件不会从 AbstractControl
接收它的 属性 值。我可以看到从未调用 ComponentA
的 registerOnChange
并且没有人订阅组件的 valueChange @Output
事件。但是,如果我在模板中静态使用 ComponentA
,所有这些都有效:调用验证器,正确传播更改等。我真的不知道我的问题是否来自 dynform
, componentA
,或两者兼而有之。
对于 dynform
我从这个模板开始:
<form (ngSubmit)="test()" [formGroup]="fgroup">
<div #container></div>
</form>
我的 dynform
代码有:
@Component({
selector: 'dynform',
templateUrl: '<form (ngSubmit)="test()" [formGroup]="fgroup"><div #container></div></form>'
]
})
export class DynForm implements OnInit, OnDestroy {
constructor( private resolver: ComponentFactoryResolver,
private view: ViewContainerRef) {
}
private mappings: any = {
'compA': { type: ComponentA },
'compB': { type: ComponentB },
...
}
@Input valuecollection: MyObjectAbstract[]; // Set by instantiator
fgroup: FormGroup;
private getComponentFactory(value: compValue): ComponentFactory<{}> {
let entry = this.mappings[value.getCompType()];
return this.resolver.resolveComponentFactory(entry.type);
}
static myValidation(control: AbstractControl): ValidationErrors | null {
let err = {
myValidationError: {
given: control.value,
max: 10,
min: 0
}
}
// Never called anyway
return control.value.toString() == "error" ? err : null;
}
ngOnInit() {
this.valuecollection.( value => {
let name = value.name;
let ref = this.container.createComponent(
this.getComponentFactory(value)
);
ref.instance.value = value;
ref.instance.name = name; // IS THIS OK?
let control = new FormControl(value, [DynForm.myValidation]);
this.fgroup.addControl(name, control);
});
}
ngOnDestroy() {
// Call the created references' destroy() method
}
}
好吧,无论如何,这就是概念。一个典型的 ComponentA
就像:
@Component({
selector: 'component-a',
templateUrl: '<stuff></stuff>',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ComponentA,
multi: true
},
]
})
export class ComponentA implements OnInit, DoCheck, ControlValueAccessor {
@Input() value: MyObjectAbstract;
@Input('formControlName') fname: string; // What?
@Output() valueChange = new EventEmitter<MyObjectAbstract>();
private propagateChange : (_: any) => {};
private _prevValue: string;
getFieldInstanceChange(): EventEmitter<FieldInstance> {
return this.fieldInstanceChange;
}
ngOnInit() {
// TODO: Connect inputFieldText in the view with the field instance (onblur?)
// console.log(`BizbookStringInputComponent()[${this.name}].ngOnInit()`);
if (this.fieldInstance && this.fieldInstance instanceof FieldInstance) {
this.inputFieldName = this.fieldInstance.base.description;
this.inputFieldText = (this.fieldInstance.value as string);
} else {
// this.inputFieldName = this.name;
this.inputFieldText = '(no field instance)';
}
}
ngDoCheck() {
if (this._prevValue == this.value.toString()) return;
if (this.propagateChange) {
// Never gets in here if added dynamically
this._prevValue = value.toString();
this.propagateChange(this.fieldInstance);
} else {
// Always gets in here if added dynamically
console.log(`propagateChange()[${this.name}].ngDoCheck(): change!: "${this.value.toString()}", but propagateChange not yet set.`);
this._prevValue = this.value.toString();
}
}
writeValue(value: any) {
if (value instanceof MyObjectAbstract && value !== this.value) {
this.value = (value as MyObjectAbstract);
}
}
registerOnChange(fn: any) {
// Never called if instantiated dynamically
this.propagateChange = fn;
}
registerOnTouched(fn: any) {
// Not used
}
}
我在 Whosebug 的某个地方读到 ControlValueAccessor
并不真正适用于动态加载的组件;这就是为什么我还实现了 valueChange @Output
。但问题似乎来自于 ngForm 验证逻辑与 @FormControlName
指令相关联的事实,我不知道如何在创建动态控件之前将其 apply/generate
我关注了 this thread,但我无法让它发挥作用。实际上我很难理解一些概念,因为我是 Angular.
的新手
几天来我一直在努力使它工作,并阅读了很多关于验证器、自定义验证器、自定义组件、动态组件等的文章,但都无济于事。非常感谢您的帮助。
看来我的整个方法都不必要地令人费解。 post 中详细解释了处理此问题的正确方法,其中还包括源代码:
https://toddmotto.com/angular-dynamic-components-forms
基本上,您需要做的是在 <ng-container YourAttribute [formControlName]="something">
上使用 ngFor 循环。 YourAttribute 是一个动态创建组件的指令。注意 [formControlName] 的 [] 语法,因为它会将值(和 FormControlName 指令!)注入 YourAttribute。
链接的项目工作得很好,但我将 ControlValueAccessor 添加到我的指令中(因为我不使用 DefaultValueAccessor)。然后我的指令需要通过 setTimeout 将 ControlValueAccessor 方法链接到实例化控件中以避免“Error: Expression has changed after it was checked. Inside of nested ControlValueAccessor”。
我正在使用 Angular 5 并且我需要创建一个组件 (dynform
) 来实例化一组自定义组件 (dyncompA
, dyncompB
, 等等)。哪些是在 dynform.ngOnInit
决定的,因此它们不会在父模板中声明,而是动态添加。每个子组件都有一个 MyObjectA
、MyObjectB
等类型的值,派生自 MyObjectAbstract
(不是字符串),我为其实现了一个 ControlValueAccessor
接口。
我遇到的问题是,父表单从未收到有关子组件的有效性状态或其已更改(!原始)状态的通知。我的自定义验证器也从未被调用过。此外,子组件不会从 AbstractControl
接收它的 属性 值。我可以看到从未调用 ComponentA
的 registerOnChange
并且没有人订阅组件的 valueChange @Output
事件。但是,如果我在模板中静态使用 ComponentA
,所有这些都有效:调用验证器,正确传播更改等。我真的不知道我的问题是否来自 dynform
, componentA
,或两者兼而有之。
对于 dynform
我从这个模板开始:
<form (ngSubmit)="test()" [formGroup]="fgroup">
<div #container></div>
</form>
我的 dynform
代码有:
@Component({
selector: 'dynform',
templateUrl: '<form (ngSubmit)="test()" [formGroup]="fgroup"><div #container></div></form>'
]
})
export class DynForm implements OnInit, OnDestroy {
constructor( private resolver: ComponentFactoryResolver,
private view: ViewContainerRef) {
}
private mappings: any = {
'compA': { type: ComponentA },
'compB': { type: ComponentB },
...
}
@Input valuecollection: MyObjectAbstract[]; // Set by instantiator
fgroup: FormGroup;
private getComponentFactory(value: compValue): ComponentFactory<{}> {
let entry = this.mappings[value.getCompType()];
return this.resolver.resolveComponentFactory(entry.type);
}
static myValidation(control: AbstractControl): ValidationErrors | null {
let err = {
myValidationError: {
given: control.value,
max: 10,
min: 0
}
}
// Never called anyway
return control.value.toString() == "error" ? err : null;
}
ngOnInit() {
this.valuecollection.( value => {
let name = value.name;
let ref = this.container.createComponent(
this.getComponentFactory(value)
);
ref.instance.value = value;
ref.instance.name = name; // IS THIS OK?
let control = new FormControl(value, [DynForm.myValidation]);
this.fgroup.addControl(name, control);
});
}
ngOnDestroy() {
// Call the created references' destroy() method
}
}
好吧,无论如何,这就是概念。一个典型的 ComponentA
就像:
@Component({
selector: 'component-a',
templateUrl: '<stuff></stuff>',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ComponentA,
multi: true
},
]
})
export class ComponentA implements OnInit, DoCheck, ControlValueAccessor {
@Input() value: MyObjectAbstract;
@Input('formControlName') fname: string; // What?
@Output() valueChange = new EventEmitter<MyObjectAbstract>();
private propagateChange : (_: any) => {};
private _prevValue: string;
getFieldInstanceChange(): EventEmitter<FieldInstance> {
return this.fieldInstanceChange;
}
ngOnInit() {
// TODO: Connect inputFieldText in the view with the field instance (onblur?)
// console.log(`BizbookStringInputComponent()[${this.name}].ngOnInit()`);
if (this.fieldInstance && this.fieldInstance instanceof FieldInstance) {
this.inputFieldName = this.fieldInstance.base.description;
this.inputFieldText = (this.fieldInstance.value as string);
} else {
// this.inputFieldName = this.name;
this.inputFieldText = '(no field instance)';
}
}
ngDoCheck() {
if (this._prevValue == this.value.toString()) return;
if (this.propagateChange) {
// Never gets in here if added dynamically
this._prevValue = value.toString();
this.propagateChange(this.fieldInstance);
} else {
// Always gets in here if added dynamically
console.log(`propagateChange()[${this.name}].ngDoCheck(): change!: "${this.value.toString()}", but propagateChange not yet set.`);
this._prevValue = this.value.toString();
}
}
writeValue(value: any) {
if (value instanceof MyObjectAbstract && value !== this.value) {
this.value = (value as MyObjectAbstract);
}
}
registerOnChange(fn: any) {
// Never called if instantiated dynamically
this.propagateChange = fn;
}
registerOnTouched(fn: any) {
// Not used
}
}
我在 Whosebug 的某个地方读到 ControlValueAccessor
并不真正适用于动态加载的组件;这就是为什么我还实现了 valueChange @Output
。但问题似乎来自于 ngForm 验证逻辑与 @FormControlName
指令相关联的事实,我不知道如何在创建动态控件之前将其 apply/generate
我关注了 this thread,但我无法让它发挥作用。实际上我很难理解一些概念,因为我是 Angular.
的新手几天来我一直在努力使它工作,并阅读了很多关于验证器、自定义验证器、自定义组件、动态组件等的文章,但都无济于事。非常感谢您的帮助。
看来我的整个方法都不必要地令人费解。 post 中详细解释了处理此问题的正确方法,其中还包括源代码:
https://toddmotto.com/angular-dynamic-components-forms
基本上,您需要做的是在 <ng-container YourAttribute [formControlName]="something">
上使用 ngFor 循环。 YourAttribute 是一个动态创建组件的指令。注意 [formControlName] 的 [] 语法,因为它会将值(和 FormControlName 指令!)注入 YourAttribute。
链接的项目工作得很好,但我将 ControlValueAccessor 添加到我的指令中(因为我不使用 DefaultValueAccessor)。然后我的指令需要通过 setTimeout 将 ControlValueAccessor 方法链接到实例化控件中以避免“Error: Expression has changed after it was checked. Inside of nested ControlValueAccessor”。