2
votes

I have a directive to build dynamic Input components for a template driven form. The default value is set by the Input component itself.

The problem is that setting a default value causes that the form is marked as dirty.

How is it possible to archieve setting a default value from inside of the Directive without marking the form as dirty?

@Directive({
  selector: '[myFormInputFactory]',
  providers: [
    { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MyFormFactoryDirective), multi: true }
  ]
})
export class MyFormFactoryDirective implements OnChanges, OnDestroy, ControlValueAccessor {
  @Input() myFormInputFactory: DialogItem;

  private componentRef: any;
  private value: any;
  private valueSubscription: Subscription;
  private disabled = false;

  constructor(
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private _renderer: Renderer,
    private _elementRef: ElementRef
  ) { }

  onChange = (_: any) => { };
  onTouched = () => { };

  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }


  ngOnChanges(changes: SimpleChanges) {
    if ('myFormInputFactory' in changes) {
      const config = changes['myFormInputFactory'].currentValue as IConfigItem;

      const factories = Array.from(this.componentFactoryResolver['_factories'].values());
      const comp = factories.find((x: any) => x.selector === config.selector) as ComponentFactory<{}>;
      const componentRef = this.viewContainerRef.createComponent(comp);

      if (this.componentRef) {
        this.componentRef.destroy();
      }
      this.componentRef = componentRef;
      this.valueSubscription = this.componentRef._component.valueChange.subscribe(value => {
        this.value = value;
        this.onChange(this.value);
      });
    }
  }

  ngOnDestroy() {
    if (this.valueSubscription) {
      this.valueSubscription.unsubscribe();
    }
  }

  writeValue(value: string): void {
    if (this.value !== null) {
      this.onChange(this.value);
    }
     if (value !== undefined && value !== null) {
       this.value = value;
    }
  }
}

UPDATE

I have created a StackBlitz

2
If you do not want to get rid with ControlValueAccessor and all the boilerplate + troubles maybe have a look into ngx-sub-form: Github (github.com/cloudnc/ngx-sub-form) Npm (npmjs.com/package/ngx-sub-form)maxime1992

2 Answers

0
votes

Could you create StackBlitz post for better debugging?

I think part of the problem might be accessing the input rather than accessing the FormControl itself. Accessing the input itself directly then triggers the onChange event and marks the input as dirty which, in my opinion, might be a problem.

How are you using the directive? Would it be possible to do the following?

  1. Create FormGroup in parent component
  2. When using myFormInputFactory directive, pass appropriate FormControl reference to the directive and assign the value to the control itself:

    this.formgroup.setValue({ key: value },{emitEvent: false})

0
votes

For what it's worth, I think the issue is because you call this.onChange within writeValue. writeValue is used to notify the control that value has been changed externally by the form, so calling onChange to notify the form about the the change is needless.