Angular4 - Немає значення доступу для контролю форми


146

У мене є спеціальний елемент:

<div formControlName="surveyType">
  <div *ngFor="let type of surveyTypes"
       (click)="onSelectType(type)"
       [class.selected]="type === selectedType">
    <md-icon>{{ type.icon }}</md-icon>
    <span>{{ type.description }}</span>
  </div>
</div>

Коли я намагаюся додати formControlName, я отримую повідомлення про помилку:

Помилка Помилка: Немає доступу до значення для управління формою з назвою: 'surveyType'

Я намагався додати ngDefaultControlбез успіху. Здається, це тому, що немає вводу / вибору ... і я не знаю, що робити.

Я хотів би прив’язати свій клік до цієї формиControl для того, щоб хтось натискав на всю карту, яка підштовхнула мій "тип" до formControl. Це можливо?


Я не знаю, що моє значення полягає в тому, що: formControl переходить на контроль форми в html, але div не є контролем форми. Мені хотілося б, щоб я зв'язав свій опис типу з type.id моєї картки div
jbtd

я знаю, що я міг би використовувати старий кутовий спосіб і прив’язати мій обраний тип, але я намагався використовувати та вивчати реактивну форму з кутового 4 і не знаю, як використовувати formControl з цим типом справи.
jbtd

Гаразд, можливо, цей випадок не може бути реалізований реактивною формою. Thx все одно :)
jbtd

Я зробив відповідь про те, як розбити величезні форми на допоміжні компоненти тут stackoverflow.com/a/56375605/2398593, але це також дуже добре застосовується лише для користувальницького доступу до значення керування. Також перегляньте github.com/cloudnc/ngx-sub-form :)
maxime1992

Відповіді:


251

Ви можете використовувати formControlNameлише директиви, які реалізують ControlValueAccessor.

Реалізуйте інтерфейс

Отже, для того, щоб робити те, що ви хочете, ви повинні створити компонент, який реалізує ControlValueAccessor, а це означає реалізувати наступні три функції :

  • writeValue (розповідає Angular, як записати значення з моделі на перегляд)
  • registerOnChange (реєструє функцію обробника, яка викликається, коли перегляд змінюється)
  • registerOnTouched (реєструє обробник, який потрібно викликати, коли компонент отримує сенсорну подію, корисну для того, щоб знати, чи компонент був зосереджений).

Зареєструйте постачальника

Потім ви повинні сказати Angular, що ця директива є ControlValueAccessor(інтерфейс не збирається її вирізати, оскільки вона знімається з коду, коли TypeScript компілюється в JavaScript). Ви робите це, зареєструвавши постачальника .

Провайдер повинен надавати NG_VALUE_ACCESSORта використовувати наявне значення . Вам також знадобиться forwardRefтут. Зауважте, що NG_VALUE_ACCESSORмусить бути мультипровайдер .

Наприклад, якщо ваша спеціальна директива названа MyControlComponent, вам слід додати щось у наступних рядках всередині об'єкта, переданого @Componentдекоратору:

providers: [
  { 
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: forwardRef(() => MyControlComponent),
  }
]

Використання

Ваш компонент готовий до використання. З формами на основі шаблонів , ngModelзв'язування тепер буде працювати належним чином.

З реактивними формами тепер ви можете правильно користуватися, formControlNameі контроль форми буде вести себе як очікувалося.

Ресурси


72

Я думаю, ви повинні використовувати formControlName="surveyType"на, inputа не наdiv


Так, звичайно, але я не знаю, як перетворити свою картотеку в щось інше, що буде керувати формою HTML
jbtd

5
Сенс CustomValueAccessor полягає в тому, щоб додати контроль форми до будь-якого, навіть
дива

4
@SoEzPz Це погана модель. Ви імітуєте функціональність введення в компоненті обгортки, самостійно повторно реалізуючи стандартні HTML-методи (таким чином, в основному заново винайдіть колесо і зробивши свій код багатослівним). але в 90% випадків ви можете виконати все, що завгодно, використовуючи <ng-content>компонент обгортки, і дозволити батьківському компоненту, який визначає, formControlsпросто помістити <input> всередину <wrapper>
Phil

3

Помилка означає, що Angular не знає, що робити, коли ви ставите formControlна div. Щоб виправити це, у вас є два варіанти.

  1. Ви поміщаєте formControlNameелемент, який підтримується кутовим з поля. Це: input, textareaі select.
  2. Ви реалізувати ControlValueAccessorінтерфейс. Роблячи це, ви говорите Angular "як отримати доступ до значення свого контролю" (звідси і назва). Або простіше кажучи: що робити, коли ви кладете formControlNameна елемент, який, природно, не має з ним пов'язаного значення.

Тепер реалізація ControlValueAccessorінтерфейсу може бути трохи непростим спочатку. Тим більше, що там не так вже й багато хорошої документації, і вам потрібно додати до коду багато котлована. Тож дозвольте мені спробувати розбити це в декількох простих для виконання кроках.

Перемістіть контроль форми у свій власний компонент

Для того, щоб реалізувати ControlValueAccessor, вам потрібно створити новий компонент (або директиву). Перемістіть туди код, пов’язаний із вашим контролем форми. Як це, це також буде легко використати повторно. Маючи керування вже всередині компонента, це може бути причиною, в першу чергу, чому вам потрібно реалізувати ControlValueAccessorінтерфейс, оскільки в іншому випадку ви не зможете використовувати свій спеціальний компонент разом із кутовими формами.

Додайте до коду плиту

Реалізація ControlValueAccessorінтерфейсу досить багатослівна, ось котельна панель, яка постачається разом із цим:

import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // a) copy paste this providers property (adjust the component name in the forward ref)
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // c) copy paste this code
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // d) copy paste this code
  writeValue(input: string) {
    // TODO
  }

То що роблять окремі частини?

  • a) Повідомляє Angular під час виконання, що ви реалізували ControlValueAccessorінтерфейс
  • b) Переконайтеся, що ви реалізуєте ControlValueAccessorінтерфейс
  • в) Це, мабуть, найбільш заплутана частина. В основному те, що ви робите, це те, що ви даєте Angular засоби для перекриття властивостей / методів вашого класу onChangeта onTouchз його власною реалізацією під час виконання, щоб потім можна було викликати ці функції. Отже, цей момент важливий для розуміння: Вам не потрібно реалізовувати onChange і onTouch (крім початкової порожньої реалізації). Єдине, що ви робите з (c) - це дозволити Angular приєднувати власні функції до свого класу. Чому? Таким чином , ви можете зателефонуватиonChange і onTouchметодам , що надаються кутовим у відповідний час. Нижче ми побачимо, як це працює.
  • г) Ми також побачимо, як writeValueпрацює метод у наступному розділі, коли ми його реалізуємо. Я розмістив його тут, тому всі необхідні властивості увімкнено ControlValueAccessor, і ваш код все ще компілюється.

Реалізувати writeValue

Що writeValueробити, це зробити щось всередині вашого спеціального компонента, коли зовнішній вигляд форми змінюється зовні . Так, наприклад, якщо ви назвали свій власний компонент управління формою, app-custom-inputі ви використовуєте його в батьківському компоненті, як це:

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

потім writeValueспрацьовує, коли батьківський компонент якимось чином змінює значення myFormControl. Це може бути, наприклад, під час ініціалізації форми ( this.form = this.formBuilder.group({myFormControl: ""});) або при скиді форми this.form.reset();.

Зазвичай ви хочете зробити, якщо значення елемента керування формою змінюється зовні - це записати його в локальну змінну, яка представляє значення керування формою. Наприклад, якщо CustomInputComponentобертається навколо текстового керування формою, це може виглядати приблизно так:

writeValue(input: string) {
  this.input = input;
}

і в html CustomInputComponent:

<input type="text"
       [ngModel]="input">

Ви також можете записати його безпосередньо на вхідний елемент, як описано в кутових документах.

Тепер ви вирішили, що відбувається всередині вашого компонента, коли зовні щось змінюється. Тепер давайте подивимось в інший бік. Як ви інформуєте зовнішній світ, коли щось змінюється всередині вашого компонента?

Виклик onChange

Наступним кроком є ​​інформування батьківського компонента про зміни всередині вашого CustomInputComponent. Тут onChangeі onTouchграють функції і функції (с) зверху. Зателефонувавши до цих функцій, ви можете повідомити зовні про зміни всередині вашого компонента. Для того, щоб поширити зміни значення назовні, вам потрібно зателефонувати onChange з новим значенням в якості аргументу . Наприклад, якщо користувач вводить щось у inputполі у вашому користувальницькому компоненті, ви дзвоните onChangeз оновленим значенням:

<input type="text"
       [ngModel]="input"
       (ngModelChange)="onChange($event)">

Якщо ви ще раз перевірте реалізацію (c), ви побачите, що відбувається: Angular прывязаний власною реалізацією до onChangeвластивості класу. Ця реалізація очікує одного аргументу, який є оновленим контрольним значенням. Що ви зараз робите, це ви називаєте цей метод і тим самим повідомляєте Angular про зміни. Кутовий тепер піде вперед і змінить форму форми зовні. Це ключова частина у всьому цьому. Ви сказали Angular, коли він повинен оновити контроль форми та з яким значенням зателефонувавшиonChange . Ви надали йому засоби "отримати доступ до контрольної величини".

До речі: ім’я onChangeобирається мною. Ви можете вибрати що завгодно, наприклад, propagateChangeподібне. Однак ви його називаєте, це буде та сама функція, яка бере один аргумент, який надається Angular і який пов'язаний з вашим класом registerOnChangeметодом під час виконання.

Дзвінок onTouch

Оскільки елементи управління формами можна "торкнутися", ви також повинні надати Angular засоби, щоб зрозуміти, коли торкнуться вашого користувацького контролю форми. Ви можете це зробити, як ви здогадалися, зателефонувавши до onTouchфункції. Отже, для нашого прикладу тут, якщо ви хочете залишатися сумісними з тим, як Angular робить це для керованих елементів керування форми, вам слід зателефонувати, onTouchколи поле введення буде розмитим:

<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">

Знову ж таки, onTouchце ім'я, вибране мною, але те, що є його фактичною функцією, надається Angular, і воно бере нульові аргументи. Що має сенс, оскільки ви просто повідомляєте Angular, що керування формою торкнулося.

Збираючи все це разом

То як це виглядає, коли все зібралося? Це повинно виглядати так:

// custom-input.component.ts
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // Step 3: Copy paste this stuff here
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  input: string;
  writeValue(input: string) {
    this.input = input;
  }

  // Step 5: Handle what should happen on the outside, if something changes on the inside
  // in this simple case, we've handled all of that in the .html
  // a) we've bound to the local variable with ngModel
  // b) we emit to the ouside by calling onChange on ngModelChange

}
// custom-input.component.html
<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>

// OR

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

Більше прикладів

Вкладені форми

Зауважте, що контрольні значення Accessors НЕ є правильним інструментом для вкладених груп форм. Для вкладених груп форм ви можете просто використовувати @Input() subformзамість цього. Принципи контрольного значення призначені для завершення controls, а не groups! Дивіться цей приклад, як використовувати вхід для вкладеної форми: https://stackblitz.com/edit/angular-nested-forms-input-2

Джерела


-1

Для мене це було пов'язано з "множиною" атрибута на вибраному вхідному контролі, оскільки Angular має різний ValueAccessor для цього типу управління.

const countryControl = new FormControl();

А всередині шаблону використовуйте так

    <select multiple name="countries" [formControl]="countryControl">
      <option *ngFor="let country of countries" [ngValue]="country">
       {{ country.name }}
      </option>
    </select>

Детальніше ref Офіційні документи


Що було пов’язано з "кратністю"? Я не бачу, як ваш код вирішує що-небудь або в чому полягала початкова проблема. Ваш код показує звичайне основне використання.
Лазар Любенович
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.