动态添加到窗体的组件的 Angular 5 粘合逻辑 [英] Angular 5 glue logic of components dynamically added to form
问题描述
我正在使用 Angular 5,我需要创建一个组件 (dynform
) 来实例化一组自定义组件 (dyncompA
、dyncompB
等).哪些是在 dynform.ngOnInit
中决定的,所以它们不是在父模板中声明而是动态添加的.每个子组件都拥有一个类型为 MyObjectA
、MyObjectB
等的值,这些值从我实现了一个 的
接口.MyObjectAbstract
(不是字符串)派生而来ControlValueAccessor
I'm using Angular 5 and I need to create a component (dynform
) that will instantiate a set of custom components (dyncompA
, dyncompB
, etc.). Which ones is decided at dynform.ngOnInit
, so they're not declared in the parent's template but added dynamically. Each child component holds a value of type MyObjectA
, MyObjectB
, etc. derived from MyObjectAbstract
(not string) for which I implemented a ControlValueAccessor
interface.
我遇到的问题是父表单永远不会被通知子组件的有效性状态,或者它们更改的 (!pristine) 状态.也从未调用过我的自定义验证器.此外,子组件不会从 AbstractControl
接收它的属性值.我可以看到 ComponentA
的 registerOnChange
从未被调用,并且没有人订阅组件的 valueChange @Output
事件.但是,如果我在模板中静态使用 ComponentA
,所有这些都有效:调用验证器,正确传播更改等.我真的不知道我的问题是否来自 dynform
、componentA
或两者.
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
. I can see that ComponentA
's registerOnChange
is never called and nobody is subscribed to the component's valueChange @Output
event. 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.
对于 dynform
我从这个模板开始:
For the dynform
I started with this template:
<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
应该是这样的:
Well, that's the concept, anyway. A typical ComponentA
would be like:
@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
}
}
我在 StackOverflow 中读到 ControlValueAccessor
并不真正适用于动态加载的组件;这就是为什么我还实现了 valueChange @Output
.但问题似乎来自这样一个事实,即 ngForm 验证逻辑与 @FormControlName
指令相关联,我不知道如何在创建之前应用/生成动态控件.
I've read somehere in StackOverflow that ControlValueAccessor
doesn't really apply to dynamically loaded components; and that's why I also implemented the valueChange @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.
我已经关注了这个线程,但我无法理解上班.实际上,我很难理解一些概念,因为我是 Angular 的新手.
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.
我几天来一直在努力使这项工作正常进行,并阅读了很多关于验证器、自定义验证器、自定义组件、动态组件等的文章,但无济于事.非常感谢您的帮助.
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
基本上,您需要做的是在
上使用 ngFor 循环.YourAttribute 是一个动态创建组件的指令.注意 [formControlName] 的 [] 语法,因为它会将值(和 FormControlName 指令!)注入到 YourAttribute 中.
Basically, what you need to do is to use an ngFor loop over an <ng-container YourAttribute [formControlName]="something">
. YourAttribute is a directive that will dynamically create the component. Notice the [] syntax for [formControlName], as it will inject the value (and the FormControlName directive!) into YourAttribute.
链接的项目运行良好,但我将 ControlValueAccessor 添加到我的指令中(因为我不使用 DefaultValueAccessor).然后我的指令需要通过 setTimeout 将 ControlValueAccessor 方法链接到实例化的控件中以避免Error: Expression has检查后更改.在嵌套的 ControlValueAccessor 内部".
The linked project works beautifully, but I added ControlValueAccessor to my directive (as I don't use 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".
这篇关于动态添加到窗体的组件的 Angular 5 粘合逻辑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!