[英]Angular 5 glue logic of components dynamically added to form
I'm using Angular 5 and I need to create a component ( dynform
) that will instantiate a set of custom components ( dyncompA
, dyncompB
, etc.). 我正在使用Angular 5 ,我需要创建一个组件(
dynform
),该组件将实例化一组自定义组件( dyncompA
, dyncompB
等)。 Which ones is decided at dynform.ngOnInit
, so they're not declared in the parent's template but added dynamically. 哪些是在
dynform.ngOnInit
,因此它们不在父级模板中声明,而是动态添加。 Each child component holds a value of type MyObjectA
, MyObjectB
, etc. derived from MyObjectAbstract
(not string) for which I implemented a ControlValueAccessor
interface. 每个子组件都具有一个值
MyObjectA
, MyObjectB
等类型的值,该值从MyObjectAbstract
(不是字符串)派生而来,我为此实现了ControlValueAccessor
接口。
The problem I get is that the parent form is never notified about the validity status of the child components, or their changed (!pristine) status. 我得到的问题是父窗体从未通知子组件的有效性状态或它们的已更改(原始状态)。 Nor my custom validator is ever called.
我的自定义验证程序也从未被调用过。 In addition, the child component doesn't receive it's property value from the
AbstractControl
. 此外,子组件不会从
AbstractControl
接收其属性值。 I can see that ComponentA
's registerOnChange
is never called and nobody is subscribed to the component's valueChange @Output
event. 我可以看到从未调用
ComponentA
的registerOnChange
并且没有人订阅该组件的valueChange @Output
事件。 However, if I use ComponentA
statically in a template, all of that works: validators are called, changes are properly propagated, etc. I don't really know if my problem comes either from the dynform
, componentA
, or both. 但是,如果我在模板中静态使用
ComponentA
,则所有这些工作都有效:调用了验证器,正确地传播了更改等。我真的不知道我的问题是否来自dynform
, componentA
或两者。
For the dynform
I started with this template: 对于
dynform
我从以下模板开始:
<form (ngSubmit)="test()" [formGroup]="fgroup">
<div #container></div>
</form>
My dynform
code has: 我的
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
}
}
Well, that's the concept, anyway. 好吧,无论如何,这就是概念。 A typical
ComponentA
would be like: 典型的
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
}
}
I've read somehere in StackOverflow that ControlValueAccessor
doesn't really apply to dynamically loaded components; 我在StackOverflow上已经读到了一些内容,即
ControlValueAccessor
并不真正适用于动态加载的组件。 and that's why I also implemented the v alueChange @Output
. 这就是为什么我还实现了
alueChange @Output
。 But the problem seems to come from the fact that the ngForm validation logic is tied to the @FormControlName
directive which I don't know how to apply/generate to the dynamic control before its creation. 但是问题似乎来自ngForm验证逻辑与
@FormControlName
指令绑定的@FormControlName
,我不知道如何在创建动态控件之前将其应用于动态控件。
I've followed this thread but I couldn't get it to work. 我遵循了这个主题,但无法正常工作。 Actually I'm struggling to understand some of the concepts because I'm new to Angular .
实际上,由于我是Angular的新手,所以我很难理解一些概念。
I've been trying to make this work for days and read a lot of articles about validators, custom validators, custom components, dynamic components, etc. to no avail. 我一直在努力工作数天,并且阅读了很多关于验证器,自定义验证器,自定义组件,动态组件等的文章,但无济于事。 I'd really appreciate your help.
非常感谢您的帮助。
It looks like my whole approach was unnecessarily convoluted. 看来我的整个方法不必要地复杂。 The correct way to handle this is very thorougly explained in this post, which also includes source code:
在这篇文章中非常彻底地解释了解决此问题的正确方法,其中还包括源代码:
https://toddmotto.com/angular-dynamic-components-forms https://toddmotto.com/angular-dynamic-components-forms
Basically, what you need to do is to use an ngFor loop over an <ng-container YourAttribute [formControlName]="something">
. 基本上,您需要做的是在
<ng-container YourAttribute [formControlName]="something">
上使用ngFor循环。 YourAttribute is a directive that will dynamically create the component. YourAttribute是将动态创建组件的指令。 Notice the [] syntax for [formControlName], as it will inject the value (and the FormControlName directive!) into YourAttribute.
注意[formControlName]的[]语法,因为它将把值(和FormControlName指令!)注入到YourAttribute中。
The linked project works beautifully, but I added ControlValueAccessor to my directive (as I don't use DefaultValueAccessor). 链接的项目工作得很漂亮,但是我将ControlValueAccessor添加到了指令中(因为我不使用DefaultValueAccessor)。 Then my directive needs to chain the ControlValueAccessor method into the instantiated control through setTimeout to avoid " Error: Expression has changed after it was checked. Inside of nested ControlValueAccessor ".
然后,我的指令需要通过setTimeout将ControlValueAccessor方法链接到实例化的控件中,以避免出现“ 错误:检查表达式后,表达式已更改。在嵌套的ControlValueAccessor内部 ”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.