Иногда мы хотим перенаправить и использовать существующий элемент управления формы, а не создавать избыточную оболочку средства доступа к значению. Одним из распространенных вариантов использования является создание, например, пользовательских компонентов input. Следующее изображение описывает нашу цель:

Мы хотим использовать элементы управления формы, переданные через директивы formControl, formControlName и ngModel, в нашем пользовательском компоненте input и перенаправить их в наш внутренний элемент input. Давайте рассмотрим два способа, которыми мы можем это сделать:

Установка свойства Control valueAccessor

Первый вариант, который мы могли бы выбрать, — это использовать NodeInjector и получить ссылку на наш элемент управления с помощью провайдера NgControl:

@Component({
  selector: 'app-input',
  template: `<input />`,
  providers: [{ 
    provide: NG_VALUE_ACCESSOR, 
    multi: true, 
    useExisting: InputComponent 
  }]
})
export class InputComponent implements ControlValueAccessor {
   const ngControl = inject(NgControl, { self: true });

   ...
}

Однако это не сработает, потому что мы получим ошибку циклической зависимости.

Это можно решить, удалив провайдера, внедрив NgControl и явно установив для свойства valueAccessor метод доступа к значению noop, поскольку нам не важно значение; мы просто хотим «удовлетворить» Angular.

class NoopValueAccessor implements ControlValueAccessor {
  writeValue() {}
  registerOnChange() {}
  registerOnTouched() {}
}

function injectNgControl() {
  const ngControl = inject(NgControl, { self: true, optional: true });

  if (!ngControl) throw new Error('...');

  if (
    ngControl instanceof FormControlDirective ||
    ngControl instanceof FormControlName ||
    ngControl instanceof NgModel
  ) {
    // 👇👇👇
    ngControl.valueAccessor = new NoopValueAccessor();

    return ngControl;
  }

  throw new Error(`...`);
}

Теперь мы можем использовать его в нашем компоненте input:

@Component({
  selector: 'app-input',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `<input [formControl]="ngControl.control" /> `,
})
export class InputComponent {
  ngControl = injectNgControl();
}

Использование директив хоста

Первый метод работает, но он всегда кажется «хакерским», поскольку мы, по сути, выполняем работу Angular, устанавливая свойство доступа к значению. Теперь мы можем использовать функцию директив хоста, чтобы получить тот же результат. Сначала мы создадим NoopValueAccessorDirective:

@Directive({
  standalone: true,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: NoopValueAccessorDirective,
    },
  ],
})
export class NoopValueAccessorDirective implements ControlValueAccessor {
  writeValue(obj: any): void {}
  registerOnChange(fn: any): void {}
  registerOnTouched(fn: any): void {}
}

Следующий шаг — создать ту же функцию injectNgControl, что и раньше, без установки свойства доступа к значению:

export function injectNgControl() {
  const ngControl = inject(NgControl, { self: true, optional: true });

  if (!ngControl) throw new Error('...');

  if (
    ngControl instanceof FormControlDirective ||
    ngControl instanceof FormControlName ||
    ngControl instanceof NgModel
  ) {
    return ngControl;
  }

  throw new Error('...');
}

Наконец, мы будем использовать его в нашем компоненте input:

@Component({
  selector: 'app-input',
  standalone: true,
  // 👇👇👇
  hostDirectives: [NoopValueAccessorDirective],
  imports: [ReactiveFormsModule],
  template: ` <input [formControl]="ngControl.control" /> `,
})
export class InputComponent {
  ngControl = injectNgControl();
}

Теперь мы можем использовать наш компонент input с любым control, который захотим:

<app-input [formControl]="control" />

<form [formGroup]="form">
    <app-input formControlName="name"></app-input>

    <ng-container formArrayName="skills">
       <app-input [formControlName]="index" 
            *ngFor="let c of skills.controls; index as index"></app-input>
    </ng-container>
</form>

Подпишитесь на меня в Medium или Twitter, чтобы узнать больше об Angular и JS!