简体   繁体   English

动态添加到组件中的Angular 5胶合逻辑组件

[英]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 ),该组件将实例化一组自定义组件( dyncompAdyncompB等)。 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. 每个子组件都具有一个值MyObjectAMyObjectB等类型的值,该值从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. 我可以看到从未调用ComponentAregisterOnChange并且没有人订阅该组件的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 ,则所有这些工作都有效:调用了验证器,正确地传播了更改等。我真的不知道我的问题是否来自dynformcomponentA或两者。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM