1. ホーム
  2. javascript

[解決済み] AngularのngDefaultControlとは何ですか?

2022-03-14 02:35:36

質問

いいえ、これは重複した質問ではありません。SOとGithubにあるたくさんの質問と問題で、私はこのディレクティブを [(ngModel)] ディレクティブがあり、フォームに含まれていない場合。これを追加しないと、エラーが発生します。

ERROR Error: No value accessor for form control with unspecified name attribute

OK、この属性を置けばエラーはなくなる。でも、ちょっと待ってください。誰もこの属性が何をするものなのか知りません! Angularのドキュメントには全く書かれていません。必要ないとわかっているのに、なぜバリューアクセッサが必要なのでしょうか?この属性はバリューアクセサとどう関係しているのでしょうか?このディレクティブは何をするのですか? バリューアクセサーとは何ですか、そしてどのように使うのですか?

そして、なぜ皆、まったく理解できないことをやり続けるのでしょうか。この行を追加すれば動く、ありがとう、こんなの良いプログラムの書き方じゃない。

そして 私が読んだのは、1つではなく Angularのフォームに関する巨大なガイド についてのセクションがあります。 ngModel :

そして、何を知っていますか?バリューアクセッサや ngDefaultControl . どこにあるんだ?

解決方法は?

[ngDefaultControl】を使用します。]

サードパーティーのコントロールには ControlValueAccessor を使用すると、angular フォームで機能します。それらの多くは、Polymerの <paper-input> のような挙動をします。 <input> ネイティブ要素を使用することができ、そのため DefaultValueAccessor . を追加する ngDefaultControl 属性があれば、そのディレクティブを使用することができます。

<paper-input ngDefaultControl [(ngModel)]="value>

または

<paper-input ngDefaultControl formControlName="name">

これが、このアトラクションが導入された最大の理由ですね。

というものでした。 ng-default-control 属性 アルファ版のangular2では .

そこで ngDefaultControl のセレクタの1つです。 デフォルト値アクセッサー ディレクティブを使用します。

@Directive({
  selector:
      'input:not([type=checkbox])[formControlName],
       textarea[formControlName],
       input:not([type=checkbox])[formControl],
       textarea[formControl],
       input:not([type=checkbox])[ngModel],
       textarea[ngModel],
       [ngDefaultControl]', <------------------------------- this selector
  ...
})
export class DefaultValueAccessor implements ControlValueAccessor {

どういう意味ですか?

これは、独自の値アクセサを持たない要素(ポリマーコンポーネントなど)にこの属性を適用できることを意味します。つまり、この要素は DefaultValueAccessor で、この要素をアンギュラーフォームで使用することができます。

を実装する必要があります。 ControlValueAccessor

ControlValueAccessor

アンギュラー ドキュメントの状態

ControlValueAccessorはAngularフォームAPIの橋渡しとして機能します。 と DOM 内のネイティブな要素との間に存在します。

簡単なangular2アプリケーションで以下のテンプレートを書いてみましょう。

<input type="text" [(ngModel)]="userName">

を理解するために、私たちの input 上記の動作は、この要素にどのディレクティブが適用されているかを知る必要があります。ここでは、angularがエラーとともにヒントを与えています。

未処理のプロミスが拒否されました。テンプレートパースエラー。にバインドできません。 ngModel' は 'input' の既知のプロパティではないので、'ngModel' を使用します。

さて、SOを開いて答えを出します:import FormsModule をあなたの @NgModule :

@NgModule({
  imports: [
    ...,
    FormsModule
  ]
})
export AppModule {}

インポートして、すべて意図したとおりに動作するようになりました。しかし、フードの下はどうなっているのでしょうか?

FormsModule は、以下のディレクティブをエクスポートします。

@NgModule({
 ...
  exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}

いくつかの調査の後、3つのディレクティブが、私たちの input

1) NgControlStatus

@Directive({
  selector: '[formControlName],[ngModel],[formControl]',
  ...
})
export class NgControlStatus extends AbstractControlStatus {
  ...
}

2) NgModel

@Directive({
  selector: '[ngModel]:not([formControlName]):not([formControl])',
  providers: [formControlBinding],
  exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges, 

3) デフォルト値アクセサー

@Directive({
  selector:
      `input:not([type=checkbox])[formControlName],
       textarea[formControlName],
       input:not([type=checkbox])formControl],
       textarea[formControl],
       input:not([type=checkbox])[ngModel],
       textarea[ngModel],[ngDefaultControl]',
  ,,,
})
export class DefaultValueAccessor implements ControlValueAccessor {

NgControlStatus ディレクティブは、以下のようなクラスを操作するだけです。 ng-valid , ng-touched , ng-dirty であり、ここでは省略できる。


DefaultValueAccesstor 提供する NG_VALUE_ACCESSOR トークンをプロバイダ配列に格納します。

export const DEFAULT_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DefaultValueAccessor),
  multi: true
};
...
@Directive({
  ...
  providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {

NgModel ディレクティブは、コンストラクタに注入されます。 NG_VALUE_ACCESSOR トークンを使用することで、同じホスト要素で宣言された

export NgModel extends NgControl implements OnChanges, OnDestroy {
 constructor(...
  @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {

私たちの場合 NgModel を注入します。 DefaultValueAccessor . そして、NgModel ディレクティブは、共有された setUpControl 関数を使用します。

export function setUpControl(control: FormControl, dir: NgControl): void {
  if (!control) _throwError(dir, 'Cannot find control with');
  if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');

  control.validator = Validators.compose([control.validator !, dir.validator]);
  control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
  dir.valueAccessor !.writeValue(control.value);

  setUpViewChangePipeline(control, dir);
  setUpModelChangePipeline(control, dir);

  ...
}

function setUpViewChangePipeline(control: FormControl, dir: NgControl): void 
{
  dir.valueAccessor !.registerOnChange((newValue: any) => {
    control._pendingValue = newValue;
    control._pendingDirty = true;

    if (control.updateOn === 'change') updateControl(control, dir);
  });
}

function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
  control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
    // control -> view
    dir.valueAccessor !.writeValue(newValue);

    // control -> ngModel
    if (emitModelEvent) dir.viewToModelUpdate(newValue);
  });
}

そして、実際に橋が架かっている様子がこちらです。

NgModel 制御を設定する (1) を呼び出し dir.valueAccessor !.registerOnChange メソッドを使用します。 ControlValueAccessor にコールバックを格納します。 onChange (2) プロパティを設定し、このコールバックを起動します。 input イベント発生 (3) . そして最後に updateControl 関数がコールバック内で呼び出されます。 (4)

function updateControl(control: FormControl, dir: NgControl): void {
  dir.viewToModelUpdate(control._pendingValue);
  if (control._pendingDirty) control.markAsDirty();
  control.setValue(control._pendingValue, {emitModelToViewChange: false});
}

angularがフォームAPIを呼び出す場所 control.setValue .

これが、その仕組みのショートバージョンです。