Apa itu ngDefaultControl di Angular?

104

Tidak, ini bukan pertanyaan duplikat. Soalnya, ada banyak pertanyaan dan masalah di SO dan Github yang meresepkan bahwa saya menambahkan arahan ini ke tag yang memiliki [(ngModel)]arahan dan tidak terkandung dalam formulir. Jika saya tidak menambahkannya, saya mendapatkan kesalahan:

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

Oke, kesalahan hilang jika saya meletakkan atribut ini di sana. Tapi tunggu! Tidak ada yang tahu apa fungsinya! Dan dokter Angular tidak menyebutkannya sama sekali. Mengapa saya membutuhkan pengakses nilai ketika saya tahu bahwa saya tidak membutuhkannya? Bagaimana atribut ini terhubung ke pengakses nilai? Apa yang dilakukan arahan ini? Apa itu pengakses nilai dan bagaimana cara menggunakannya?

Dan mengapa setiap orang terus melakukan hal-hal yang sama sekali tidak mereka mengerti? Tambahkan saja baris kode ini dan berhasil, terima kasih, ini bukan cara untuk menulis program yang bagus.

Lalu. Saya membaca bukan hanya satu tapi dua panduan besar tentang bentuk di Angular dan bagian tentang ngModel:

Dan tahukah Anda? Tidak satu pun penyebutan pengakses nilai atau ngDefaultControl. Dimana itu?

Gherman
sumber
> "Dan mengapa setiap orang terus melakukan hal-hal yang mereka tidak mengerti sama sekali?" - Iya! persis! ini bisa menggunakan beberapa tanda seru lagi ;-)
Guss

Jawaban:

181

[ngDefaultControl]

Kontrol pihak ketiga membutuhkan a ControlValueAccessorberfungsi dengan bentuk sudut. Banyak dari mereka, seperti Polymer <paper-input>, berperilaku seperti <input>elemen asli dan dengan demikian dapat menggunakan DefaultValueAccessor. Menambahkan ngDefaultControlatribut akan memungkinkan mereka menggunakan arahan itu.

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

atau

<paper-input ngDefaultControl formControlName="name">

Jadi inilah alasan utama mengapa atribut ini diperkenalkan.

Itu disebut ng-default-controlatribut dalam versi alfa angular2 .

Jadi ngDefaultControladalah salah satu pemilih untuk direktif DefaultValueAccessor :

@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 {

Apa artinya?

Artinya kita dapat menerapkan atribut ini ke elemen (seperti komponen polimer) yang tidak memiliki pengakses nilainya sendiri. Jadi elemen ini akan mengambil perilaku DefaultValueAccessordan kita dapat menggunakan elemen ini dengan bentuk sudut.

Jika tidak, Anda harus menyediakan implementasi Anda sendiri ControlValueAccessor

ControlValueAccessor

Status dokumen sudut

ControlValueAccessor bertindak sebagai jembatan antara Angular forms API dan elemen asli di DOM.

Mari tulis template berikut dalam aplikasi angular2 sederhana:

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

Untuk memahami bagaimana inputperilaku kita di atas, kita perlu mengetahui arahan mana yang diterapkan pada elemen ini. Di sini angular memberikan beberapa petunjuk dengan kesalahan:

Penolakan Janji Tak Tertangani: Kesalahan penguraian template: Tidak dapat mengikat ke 'ngModel' karena bukan properti 'input' yang diketahui.

Oke, kita bisa buka SO dan dapatkan jawabannya: import FormsModuleto your @NgModule:

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

Kami mengimpornya dan semua bekerja sebagaimana mestinya. Tapi apa yang terjadi di balik terpal?

FormsModule mengekspor untuk kita arahan berikut:

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

masukkan deskripsi gambar di sini

Setelah beberapa penyelidikan kita dapat menemukan bahwa tiga arahan akan diterapkan pada kita 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) DEFAULT_VALUE_ACCESSOR

@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 {

NgControlStatusdirektif hanya memanipulasi kelas seperti ng-valid, ng-touched, ng-dirtydan kita dapat menghilangkan sini.


DefaultValueAccesstormenyediakan NG_VALUE_ACCESSORtoken dalam array penyedia:

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 {

NgModeldirektif memasukkan NG_VALUE_ACCESSORtoken konstruktor yang dideklarasikan pada elemen host yang sama.

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

Dalam kasus kami NgModelakan menyuntikkan DefaultValueAccessor. Dan sekarang direktif NgModel memanggil setUpControlfungsi bersama :

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);
  });
}

Dan inilah jembatan yang sedang beraksi:

masukkan deskripsi gambar di sini

NgModelmengatur kontrol (1) dan dir.valueAccessor !.registerOnChangemetode panggilan . ControlValueAccessormenyimpan callback dalam properti onChange(2) dan mengaktifkan callback ini ketika inputperistiwa terjadi (3) . Dan akhirnya updateControlfungsi dipanggil di dalam callback (4)

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

di mana panggilan sudut membentuk API control.setValue.

Itu adalah versi singkat dari cara kerjanya.

yurzui
sumber
Saya baru saja membuat @Input() ngModeldan @Output() ngModelChangeuntuk pengikatan dua arah dan saya pikir itu harus cukup jembatan. Sepertinya melakukan hal yang sama dengan cara yang sama sekali berbeda. Mungkin saya tidak harus memberi nama bidang saya ngModel?
Gherman
3
Jika Anda tidak menggunakan komponen ini dengan bentuk sudut maka Anda dapat membuat penjilidan dua arah Anda sendiri seperti @Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();dan kemudian gunakan saja[(value)]="someProp"
yurzui
2
Itulah yang saya lakukan. Tapi saya menamai "nilai" saya sebagai ngModeldan Angular mulai membuat kesalahan pada saya dan meminta dengan ControlValueAccessor.
Gherman
Adakah yang setara dengan ngDefaultControl di vue dan React? Maksud saya, saya membuat komponen masukan khusus dalam sudut menggunakan pengakses nilai kontrol dan membungkusnya sebagai komponen web dalam elemen sudut. Dalam proyek yang sama saya harus menggunakan ngDefaultControl untuk membuatnya bekerja dengan bentuk sudut. Tapi apa yang harus saya lakukan untuk membuatnya berfungsi di Vue dan React? Juga di JS asli?
Kavinda Jayakody
Saya menggunakan ngDefaultControl pada komponen kustom saya tetapi saya kesulitan dengan satu masalah. Ketika saya menetapkan nilai default untuk formControl di dalam tampilan formBuilder saya (komponen input kustom) tidak diperbarui, hanya model. Apa yang saya lakukan salah?
Igor Janković