Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changes ngControl.valueAccessor for dynamically changes component

In my Angular app I have a form with dynamically changes input components. All dynamic components are implements ControlValueAccessor.

If you input value in Input and next change component (click button "change component to number"), then ngControl don't change his reference valueAccessor to new component. And my new Input component will not change my model. What am I doing wrong ? I have a example in a stackblitz. And I have an example code.

My dynamic form component:

@Component({
  selector: "app-form-control-outlet",
  template: ``,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FormControlOutletComponent),
      multi: true
    }
  ]
})
export class FormControlOutletComponent implements OnChanges {
  @Input() component = CustomStringInputComponent;

  componentRef: ComponentRef<any>;

  constructor(
    public injector: Injector,
    private container: ViewContainerRef,
    private resolver: ComponentFactoryResolver,
    private viewContainerRef: ViewContainerRef
  ) {}

  public ngOnChanges(changes: SimpleChanges): void {
    const factory = this.resolver.resolveComponentFactory(this.component);

    const componentFactory = this.resolver.resolveComponentFactory(
      this.component
    );

    if (this.container.length > 0) {
      this.container.clear();
    }

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

    const ngControl = this.injector.get(NgControl);
    ngControl.valueAccessor = this.componentRef.instance;
  }
}

And one of my dynamic Input

 @Component({
  selector: "app-custom-input",
  template: `
    <input
      [(ngModel)]="value"
      (ngModelChange)="onValueChange($event)"
      (blur)="onInputBlurred()"
    />
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomStringInputComponent),
      multi: true
    }
  ]
})
export class CustomStringInputComponent implements ControlValueAccessor {
  public value: string;
  public onChange: (value: string) => void;
  public onTouched: () => void;

  public writeValue(value: string): void {
    this.value = value;
  }

  public registerOnChange(fn: (value: string) => void): void {
    this.onChange = fn;
  }

  public registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  public onValueChange(value: string): void {
    this.writeValue(value);
    this.onChange(value);
  }

  public onInputBlurred(): void {
    this.onTouched();
  }
}
like image 221
annet Avatar asked May 17 '26 15:05

annet


1 Answers

This happens because when you change valueAccessor Angular doesn't register onChange and onTouched events on that new component again.

What you can try as workaround is to register those events manually:

const ngControl = this.injector.get(NgControl);
const valueAccessor = ngControl.valueAccessor as any;
if (valueAccessor && valueAccessor.onChange) {
  this.componentRef.instance.registerOnChange(valueAccessor.onChange);
  this.componentRef.instance.registerOnTouched(valueAccessor.onTouched);
}
ngControl.valueAccessor = this.componentRef.instance;

Forked Stackblitz

like image 134
yurzui Avatar answered May 19 '26 03:05

yurzui