如何动态地将NG_VALUE_ACCESSOR组件添加到被动表单?

时间:2017-05-25 12:55:43

标签: angular typescript

我们希望使用NG_VALUE_ACCESSORComponentFactoryResolver的自定义指令动态地将ViewContainerRef组件添加到被动表单中。问题是我们无法将formControlName分配给动态添加的组件,我们无法从组件中获取访问者值。

我们尝试了几种不同的选项,但它们都没有为我们工作(直接将formControlName添加到ngContainer会引发错误,也是ngComponentOutlet的一个选项,但是我们无法为组件提供参数)。

我们在plunker中创建了一个静态测试用例(我们想要达到的结果)和一个使用指令的动态测试用例,我们无法将formControlName分配给组件。我们将在下面的评论中提供链接。

2 个答案:

答案 0 :(得分:4)

您可以尝试扩展NgControl。这是简单的实现。但它可能更复杂。

<强>动态panel.directive.ts

@Directive({
    selector: '[dynamic-panel]'
})
export class DynamicPanelDirective extends NgControl implements OnInit  {

    name: string;

    component: ComponentRef<any>;

    @Output('ngModelChange') update = new EventEmitter();

    _control: FormControl;

    constructor(
        @Optional() @Host() @SkipSelf() private parent: ControlContainer,
        private resolver: ComponentFactoryResolver,
        private container: ViewContainerRef) {
        super();
    }

    ngOnInit() {
        let component = this.resolver.resolveComponentFactory<GeneralPanelComponent>(GeneralPanelComponent);
        this.name = 'general';
        this.component = this.container.createComponent(component);
        this.valueAccessor = this.component.instance;
        this._control = this.formDirective.addControl(this);
    }

    get path(): string[] {
        return [...this.parent.path !, this.name];
    }

    get formDirective(): any { return this.parent ? this.parent.formDirective : null; }

    get control(): FormControl { return this._control; }

    get validator(): ValidatorFn|null { return null; }

    get asyncValidator(): AsyncValidatorFn { return null; }

    viewToModelUpdate(newValue: any): void {
        this.update.emit(newValue);
    }

    ngOnDestroy(): void {
        if (this.formDirective) {
            this.formDirective.removeControl(this);
        }
        if(this.component) {
            this.component.destroy();
        }
    }
}

<强> Modified Plunker

所以

  

如何动态地将NG_VALUE_ACCESSOR组件添加到被动表单?

this.valueAccessor = this.component.instance;

在我的情况下

如果您想使用验证器,请参阅 Plunker

答案 1 :(得分:3)

当前接受的答案适用于原始帖子中的确切场景,但是略有不同的场景将我带到了该帖子中。感谢@yurzui,我得以根据他的答案找到解决方案。

我自己的解决方案允许使用模板中的常规声明性绑定将完整的集成(包括ngModel,反应形式和验证器)集成到Angular形式生态系统中。因此,我将其发布在这里,以防其他人来这里寻找。

您可以在StackBlitz上签出。

import {
    Component,
    ComponentFactoryResolver,
    forwardRef,
    Host,
    Injector,
    SkipSelf,
    ViewContainerRef,
} from '@angular/core';
import { ControlContainer, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';

import { CustomInputComponent } from './custom-input.component';

@Component({
    selector: 'app-form-control-outlet',
    template: ``,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => FormControlOutletComponent),
            multi: true,
        },
    ],
})
export class FormControlOutletComponent {
    constructor(
        public injector: Injector,
        private componentFactoryResolver: ComponentFactoryResolver,
        private viewContainerRef: ViewContainerRef,
    ) {}

    public ngOnInit(): void {
        const ngControl = this.injector.get(NgControl);
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
            /**
              * Retrieve this component in whatever way you would like,
              * it could be based on an @Input or from a service etc...
              */
            CustomInputComponent,
        );

        const componentRef = this.viewContainerRef.createComponent(
            componentFactory,
        );

        ngControl.valueAccessor = componentRef.instance;
    }
}