NgDefaultControl trong Angular là gì?


100

Không, đây không phải là một câu hỏi trùng lặp. Bạn thấy đấy, có rất nhiều câu hỏi và vấn đề trong SO và Github quy định rằng tôi thêm chỉ thị này vào thẻ có [(ngModel)]chỉ thị và không được chứa trong biểu mẫu. Nếu tôi không thêm nó, tôi sẽ gặp lỗi:

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

Được rồi, lỗi sẽ biến mất nếu tôi đặt thuộc tính này ở đó. NHƯNG, đợi đã! Không ai biết nó làm gì! Và tài liệu của Angular hoàn toàn không đề cập đến nó. Tại sao tôi cần một trình truy cập giá trị khi tôi biết rằng tôi không cần nó? Thuộc tính này được kết nối với trình truy cập giá trị như thế nào? Chỉ thị này làm gì? Giá trị acessor là gì và tôi sử dụng nó như thế nào?

Và tại sao mọi người cứ làm những việc mà họ không hiểu hết? Chỉ cần thêm dòng mã này và nó hoạt động, cảm ơn bạn, đây không phải là cách để viết chương trình tốt.

Và sau đó. Tôi đã đọc không phải một mà là hai hướng dẫn khổng lồ về các biểu mẫu trong Angular một phần về ngModel:

Và bạn biết những gì? Không đề cập đến một trong hai trình truy cập giá trị hoặc ngDefaultControl. Nó đâu rồi?

Câu trả lời:


178

[ngDefaultControl]

Các điều khiển của bên thứ ba yêu cầu một ControlValueAccessorchức năng với các dạng góc cạnh. Nhiều người trong số họ, như Polymer <paper-input>, hoạt động giống như <input>phần tử gốc và do đó có thể sử dụng DefaultValueAccessor. Thêm một ngDefaultControlthuộc tính sẽ cho phép họ sử dụng chỉ thị đó.

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

hoặc là

<paper-input ngDefaultControl formControlName="name">

Vì vậy, đây là lý do chính tại sao loại dầu này được giới thiệu.

Nó được gọi là ng-default-controlthuộc tính trong các phiên bản alpha của angle2 .

Vì vậy, ngDefaultControllà một trong những chọn lọc dùng cho DefaultValueAccessor chỉ thị:

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

Nó có nghĩa là gì?

Có nghĩa là chúng ta có thể áp dụng thuộc tính này cho phần tử (như thành phần polyme) không có bộ truy cập giá trị riêng của nó. Vì vậy, phần tử này sẽ lấy hành vi từ đó DefaultValueAccessorvà chúng ta có thể sử dụng phần tử này với các dạng góc.

Nếu không, bạn phải cung cấp cách triển khai của riêng mình ControlValueAccessor

ControlValueAccessor

Trạng thái tài liệu góc

ControlValueAccessor hoạt động như một cầu nối giữa API biểu mẫu Angular và một phần tử gốc trong DOM.

Hãy viết mẫu sau trong ứng dụng angle2 đơn giản:

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

Để hiểu cách inputở trên của chúng ta sẽ hoạt động như thế nào, chúng ta cần biết các lệnh nào được áp dụng cho phần tử này. Ở đây góc đưa ra một số gợi ý về lỗi:

Từ chối Promise chưa được xử lý: Lỗi phân tích cú pháp mẫu: Không thể liên kết với 'ngModel' vì nó không phải là thuộc tính đã biết của 'input'.

Được rồi, chúng ta có thể mở SO và nhận được câu trả lời: nhập FormsModulevào @NgModule:

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

Chúng tôi đã nhập nó và tất cả hoạt động như dự định. Nhưng điều gì đang xảy ra dưới mui xe?

FormsModule xuất cho chúng tôi các chỉ thị sau:

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

nhập mô tả hình ảnh ở đây

Sau một số cuộc điều tra, chúng tôi có thể phát hiện ra rằng ba chỉ thị sẽ được áp dụng cho 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 {

NgControlStatuschỉ thao túng thị lớp thích ng-valid, ng-touched, ng-dirtyvà chúng ta có thể bỏ qua nó ở đây.


DefaultValueAccesstorcung cấp NG_VALUE_ACCESSORmã thông báo trong mảng nhà cung cấp:

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 {

NgModelchỉ thị đưa vào NG_VALUE_ACCESSORmã thông báo khởi tạo đã được khai báo trên cùng một phần tử máy chủ.

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

Trong trường hợp của chúng tôi NgModelsẽ tiêm DefaultValueAccessor. Và bây giờ lệnh NgModel gọi setUpControlhàm chia sẻ :

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

Và đây là cây cầu đang hoạt động:

nhập mô tả hình ảnh ở đây

NgModelthiết lập điều khiển (1)dir.valueAccessor !.registerOnChangephương thức gọi . ControlValueAccessorlưu trữ lệnh gọi lại trong thuộc tính onChange(2) và kích hoạt lệnh gọi lại này khi inputsự kiện xảy ra (3) . Và cuối cùng updateControlhàm được gọi bên trong callback (4)

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

nơi các lệnh gọi góc tạo thành API control.setValue.

Đó là một phiên bản ngắn về cách nó hoạt động.


Tôi chỉ thực hiện @Input() ngModel@Output() ngModelChangecho ràng buộc hai chiều và tôi nghĩ rằng nó phải là đủ một cầu nối. Điều này trông giống như làm điều tương tự theo một cách hoàn toàn khác. Có lẽ tôi không nên đặt tên cho lĩnh vực của mình ngModel?
Gherman

2
Nếu bạn không sử dụng thành phần này với các hình thức góc sau đó bạn chỉ có thể tạo riêng hai chiều của bạn ràng buộc như @Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();và sau đó chỉ cần sử dụng[(value)]="someProp"
yurzui

1
Đó chính xác là những gì tôi đang làm. Nhưng tôi đã đặt tên "giá trị" của mình là ngModelvà Angular bắt đầu ném lỗi cho tôi và yêu cầu với ControlValueAccessor.
Gherman

Có ai tương đương với ngDefaultControl trong vue và React không? Ý tôi là, tôi đã tạo một thành phần đầu vào tùy chỉnh trong góc độ bằng cách sử dụng trình truy cập giá trị điều khiển và bọc nó như một thành phần web trong phần tử Angular. Trong cùng một dự án, tôi phải sử dụng ngDefaultControl để làm cho nó hoạt động với các dạng góc. Nhưng tôi nên làm gì để chúng hoạt động trong Vue và React? Cũng trong JS gốc?
Kavinda Jayakody

Tôi đang sử dụng ngDefaultControl trên thành phần tùy chỉnh của mình nhưng tôi phải vật lộn với một vấn đề. Khi tôi đặt giá trị mặc định cho formControl bên trong dạng xem formBuilder của tôi (thành phần đầu vào tùy chỉnh) không được cập nhật, chỉ có mô hình. Tôi đang làm gì sai?
Igor Janković
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.