ngDefaultControl ใน Angular คืออะไร?


117

ไม่นี่ไม่ใช่คำถามที่ซ้ำกัน คุณจะเห็นว่ามีคำถามและปัญหามากมายใน SO และ Github ที่กำหนดให้ฉันเพิ่มคำสั่งนี้ลงในแท็กที่มี[(ngModel)]คำสั่งและไม่มีอยู่ในรูปแบบ หากฉันไม่เพิ่มจะได้รับข้อผิดพลาด:

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

โอเคข้อผิดพลาดจะหายไปถ้าฉันใส่แอตทริบิวต์นี้ไว้ที่นั่น แต่เดี๋ยวก่อน! ไม่มีใครรู้ว่ามันทำอะไร! และเอกสารของ Angular ไม่ได้กล่าวถึงเลย เหตุใดฉันจึงต้องมีตัวเข้าถึงค่าเมื่อฉันรู้ว่าฉันไม่ต้องการมัน แอตทริบิวต์นี้เชื่อมต่อกับตัวเข้าถึงค่าอย่างไร คำสั่งนี้ทำอะไร? ตัวเข้าถึงค่าคืออะไรและฉันจะใช้มันได้อย่างไร?

แล้วทำไมทุกคนถึงทำในสิ่งที่พวกเขาไม่เข้าใจอยู่เสมอ? เพียงเพิ่มบรรทัดของโค้ดนี้และใช้งานได้ขอบคุณนี่ไม่ใช่วิธีการเขียนโปรแกรมที่ดี

แล้ว. ผมอ่านไม่หนึ่ง แต่สองคำแนะนำเกี่ยวกับรูปแบบขนาดใหญ่ในเชิงมุมและส่วนที่เกี่ยวกับngModel:

และคุณรู้อะไรไหม? ไม่ได้กล่าวถึงผู้เข้าถึงคุณค่าหรือngDefaultControl. มันอยู่ที่ไหน?


1
> "แล้วทำไมทุกคนถึงทำในสิ่งที่พวกเขาไม่เข้าใจอยู่เรื่อย ๆ " - ใช่! เป๊ะ! มันสามารถใช้เครื่องหมายอัศเจรีย์ได้มากกว่านี้ ;-)
Guss

คำตอบ:


200

[ngDefaultControl]

การควบคุมของบุคคลที่สามต้องการControlValueAccessorฟังก์ชันในรูปแบบเชิงมุม หลายคนเช่นพอลิเมอ<paper-input>ร์ทำงานเหมือน<input>องค์ประกอบดั้งเดิมดังนั้นจึงสามารถใช้ไฟล์DefaultValueAccessor. การเพิ่มngDefaultControlแอตทริบิวต์จะช่วยให้สามารถใช้คำสั่งนั้นได้

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

หรือ

<paper-input ngDefaultControl formControlName="name">

นี่จึงเป็นเหตุผลหลักว่าทำไมจึงมีการนำคุณสมบัตินี้มาใช้

มันถูกเรียกว่าng-default-controlแอตทริบิวต์ในอัลฟารุ่น angular2

ดังนั้นจึงngDefaultControlเป็นหนึ่งในตัวเลือกสำหรับคำสั่ง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 {

หมายความว่าอย่างไร?

หมายความว่าเราสามารถใช้แอตทริบิวต์นี้กับองค์ประกอบ (เช่นส่วนประกอบโพลีเมอร์) ที่ไม่มีตัวเข้าถึงค่าของตัวเอง ดังนั้นองค์ประกอบนี้จะรับพฤติกรรมDefaultValueAccessorและเราสามารถใช้องค์ประกอบนี้กับรูปแบบเชิงมุมได้

มิฉะนั้นคุณต้องจัดเตรียมการใช้งานของคุณเอง ControlValueAccessor

ControlValueAccessor

เอกสารเชิงมุมระบุ

ControlValueAccessor ทำหน้าที่เป็นสะพานเชื่อมระหว่าง API รูปแบบเชิงมุมกับองค์ประกอบดั้งเดิมใน DOM

มาเขียนเทมเพลตต่อไปนี้ในแอปพลิเคชั่น angular2 อย่างง่าย:

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

เพื่อให้เข้าใจถึงinputพฤติกรรมข้างต้นของเราเราจำเป็นต้องรู้ว่าคำสั่งใดที่ใช้กับองค์ประกอบนี้ ที่นี่เชิงมุมให้คำใบ้กับข้อผิดพลาด:

การปฏิเสธสัญญาที่ไม่สามารถจัดการได้: ข้อผิดพลาดในการแยกวิเคราะห์เทมเพลต: ไม่สามารถผูกกับ 'ngModel' ได้เนื่องจากไม่ใช่คุณสมบัติที่เป็นที่รู้จักของ 'input'

โอเคเราสามารถเปิด SO และรับคำตอบ: นำเข้าFormsModuleสู่@NgModule:

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

เรานำเข้าและทำงานได้ตามที่ตั้งใจไว้ แต่เกิดอะไรขึ้นภายใต้ประทุน?

FormsModuleส่งออกคำสั่งต่อไปนี้สำหรับเรา:

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

ป้อนคำอธิบายภาพที่นี่

หลังจากการตรวจสอบเราพบว่าคำสั่งสามข้อจะถูกนำไปใช้กับ 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 {

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 directive เรียกใช้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ฟังก์ชั่นถูกเรียกภายใน callback (4)

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

ที่โทรเชิงมุมรูปแบบ control.setValueAPI

นี่เป็นเวอร์ชันสั้น ๆ ของวิธีการทำงาน


ฉันเพิ่งสร้าง@Input() ngModelและ@Output() ngModelChangeสำหรับการผูกแบบสองทิศทางและฉันคิดว่ามันน่าจะเพียงพอแล้ว ดูเหมือนว่าการทำสิ่งเดียวกันในวิธีที่แตกต่างกันอย่างสิ้นเชิง บางทีฉันไม่ควรตั้งชื่อฟิลด์ของฉันngModel?
Gherman

3
หากคุณไม่ได้ใช้ส่วนประกอบนี้กับรูปแบบเชิงมุมคุณก็สามารถสร้างการผูกแบบสองทางของคุณเองได้@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();จากนั้นก็ใช้[(value)]="someProp"
yurzui

2
นั่นคือสิ่งที่ฉันกำลังทำอยู่ แต่ฉันตั้งชื่อ "ค่า" ของฉันเป็นngModelและ Angular เริ่มส่งข้อผิดพลาดมาที่ฉันและขอด้วย ControlValueAccessor
Gherman

ใครบ้างที่เทียบเท่ากับ ngDefaultControl ใน vue และ React? ฉันหมายความว่าฉันสร้างส่วนประกอบอินพุตที่กำหนดเองในเชิงมุมโดยใช้ตัวเข้าถึงค่าควบคุมและรวมเป็นส่วนประกอบเว็บในองค์ประกอบเชิงมุม ในโครงการเดียวกันฉันต้องใช้ ngDefaultControl เพื่อให้ทำงานกับรูปแบบเชิงมุม แต่ฉันควรทำอย่างไรเพื่อให้มันทำงานใน Vue และ React ยังอยู่ใน JS ดั้งเดิม?
Kavinda Jayakody

ฉันใช้ ngDefaultControl กับส่วนประกอบที่กำหนดเอง แต่ฉันประสบปัญหาหนึ่ง เมื่อฉันตั้งค่าเริ่มต้นสำหรับ formControl ภายในมุมมอง formBuilder ของฉัน (คอมโพเนนต์การป้อนข้อมูลที่กำหนดเอง) ไม่ได้รับการปรับปรุงรุ่นเท่านั้น ผมทำอะไรผิดหรือเปล่า?
Igor Janković
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.