动态添加到窗体的组件的 Angular 5 粘合逻辑 [英] Angular 5 glue logic of components dynamically added to form

查看:26
本文介绍了动态添加到窗体的组件的 Angular 5 粘合逻辑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Angular 5,我需要创建一个组件 (dynform) 来实例化一组自定义组件 (dyncompAdyncompB 等).哪些是在 dynform.ngOnInit 中决定的,所以它们不是在父模板中声明而是动态添加的.每个子组件都拥有一个类型为 MyObjectAMyObjectB 等的值,这些值从我实现了一个 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 接收它的属性值.我可以看到 ComponentAregisterOnChange 从未被调用,并且没有人订阅组件的 valueChange @Output 事件.但是,如果我在模板中静态使用 ComponentA,所有这些都有效:调用验证器,正确传播更改等.我真的不知道我的问题是否来自 dynformcomponentA 或两者.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆