Platon Rov
Platon Rov

Reputation: 323

How to touch inner control in custom control component in Angular?

I have a form with my own custom control component:

@Component({
  selector: "my-app",
  template: `
    <form [formGroup]="form">
      <app-custom-control formControlName="customControl"></app-custom-control>
    </form>

    <button (click)="touch()">
      Touch!
    </button>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.form = this.fb.group({
      customControl: "1"
    });
  }

  touch() {
    this.form.markAllAsTouched();
  }
}

My custom control component in its turn has another custom control component inside (in my real app it's needed because the outer control component has two modes - read & edit):

@Component({
  selector: "app-custom-control",
  template: `
    <ng-select [ngModel]="value" [items]="items"></ng-select>
  `,
  styleUrls: ["./custom-control.component.css"],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: CustomControlComponent,
      multi: true
    }
  ]
})
export class CustomControlComponent implements ControlValueAccessor, OnInit {
  items = ["1", "2"];

  value = null;
  onChange = (value: any) => {};
  onTouched = () => {};

  constructor() {}

  registerOnChange(fn: any) {
    this.onChange = fn;
  }

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

  writeValue(outsideValue: number) {
    this.value = outsideValue;
  }
}

It's possible to add NG_VALIDATORS to CustomControlComponent and implement how the component should be validated (it can just lift up ng-select errors). But i'm really don't know how to touch inner component (ng-select) when doing this.form.markAllAsTouched() in the component which wraps the form.

I've tried to do a manipulations in ngDoCheck lifecycle method but it seems runs too much times what isn't acceptable solution.

Playground: https://stackblitz.com/edit/angular-ivy-u3kdfj

Upvotes: 3

Views: 1699

Answers (1)

yurzui
yurzui

Reputation: 214017

There is several relevant issues for that in github:

What you can do is to get hold of NgControl for ControlValueAccessor control(outerControl) and for inner NgModel control(innerControl):

custom-control.component.ts

export class CustomControlComponent implements ControlValueAccessor, AfterViewInit {
  @ViewChild(NgControl) innerNgControl: NgControl;

  constructor(private inj: Injector) {}

  ...

  ngAfterViewInit() { 
    const outerControl = this.inj.get(NgControl).control;
    ...
  }
}

Then you can bypass touched state to inner control by using this trick:

const prevMarkAsTouched = outerControl.markAsTouched;
outerControl.markAsTouched = (...args: any) => { 
  this.innerNgControl.control.markAsTouched();
  prevMarkAsTouched.bind(outerControl)(...args);
};

Forked Stackblitz

Upvotes: 5

Related Questions