2017-05-26 14 views
12

Tôi muốn tạo phần tử biểu mẫu tùy chỉnh với giao diện ControlValueAccessor trong Angular 2+. Phần tử này sẽ là một trình bao bọc trên <select>. Có thể truyền các đặc tính formControl đến phần tử được bọc không? Trong trường hợp của tôi, trạng thái xác thực không được nhân giống để chọn lồng nhau như bạn có thể thấy trên ảnh chụp màn hình đính kèm.Tôi có thể truy cập vào formControl của ControlValueAccessor tùy chỉnh của tôi trong Angular 2+ không?

enter image description here

thành phần của tôi là có sẵn như sau:

const OPTIONS_VALUE_ACCESSOR: any = { 
    multi: true, 
    provide: NG_VALUE_ACCESSOR, 
    useExisting: forwardRef(() => OptionsComponent) 
    }; 

    @Component({ 
    providers: [OPTIONS_VALUE_ACCESSOR], 
    selector: 'inf-select[name]', 
    templateUrl: './options.component.html' 
    }) 
    export class OptionsComponent implements ControlValueAccessor, OnInit { 

    @Input() name: string; 
    @Input() disabled = false; 
    private propagateChange: Function; 
    private onTouched: Function; 

    private settingsService: SettingsService; 
    selectedValue: any; 

    constructor(settingsService: SettingsService) { 
    this.settingsService = settingsService; 
    } 

    ngOnInit(): void { 
    if (!this.name) { 
    throw new Error('Option name is required. eg.: <options [name]="myOption"></options>>'); 
    } 
    } 

    writeValue(obj: any): void { 
    this.selectedValue = obj; 
    } 

    registerOnChange(fn: any): void { 
    this.propagateChange = fn; 
    } 

    registerOnTouched(fn: any): void { 
    this.onTouched = fn; 
    } 

    setDisabledState(isDisabled: boolean): void { 
    this.disabled = isDisabled; 
    } 
    } 

Đây là mẫu thành phần của tôi:

<select class="form-control" 
    [disabled]="disabled" 
    [(ngModel)]="selectedValue" 
    (ngModelChange)="propagateChange($event)"> 
    <option value="">Select an option</option> 
    <option *ngFor="let option of settingsService.getOption(name)" [value]="option.description"> 
    {{option.description}} 
    </option> 
    </select> 
+0

Bạn có thể tạo lại nó trong máy xếp không? – yurzui

Trả lời

5

SAMPLE PLUNKER

tôi thấy hai lựa chọn:

  1. Tuyên truyền các lỗi từ phần FormControl để <select>FormControl bất cứ khi nào giá trị <select>FormControl thay đổi
  2. Tuyên truyền các xác nhận từ phần FormControl để <select>FormControl

Bellow các biến sau đây có sẵn:

  • selectModelNgModel của <select>
  • formControlFormControl của các thành phần nhận như một cuộc tranh cãi

Lựa chọn 1: tuyên truyền lỗi

ngAfterViewInit(): void { 
    this.selectModel.control.valueChanges.subscribe(() => { 
     this.selectModel.control.setErrors(this.formControl.errors); 
    }); 
    } 

Phương án 2: tuyên truyền Validators

ngAfterViewInit(): void { 
    this.selectModel.control.setValidators(this.formControl.validator); 
    this.selectModel.control.setAsyncValidators(this.formControl.asyncValidator); 
    } 

Sự khác biệt giữa hai là tuyên truyền các lỗi có nghĩa là đã có lỗi, trong khi tùy chọn giây liên quan đến việc thực hiện các trình xác nhận lần thứ hai. Một số trong số đó, như trình xác nhận không đồng bộ có thể quá tốn kém để thực hiện.

Tuyên truyền tất cả các thuộc tính?

Không có giải pháp chung để truyền bá tất cả các thuộc tính. Các thuộc tính khác nhau được thiết lập bởi các chỉ thị khác nhau, hoặc các phương tiện khác, do đó có vòng đời khác nhau, có nghĩa là yêu cầu xử lý cụ thể. Giải pháp hiện tại liên quan đến việc truyền bá các lỗi xác thực và trình xác nhận hợp lệ. Có nhiều tài sản có sẵn ở đó.

Lưu ý rằng bạn có thể nhận được các thay đổi trạng thái khác nhau từ cá thể FormControl bằng cách đăng ký FormControl.statusChanges(). Bằng cách này bạn có thể nhận được điều khiển là VALID, INVALID, DISABLED hoặc PENDING (xác thực không đồng bộ vẫn đang chạy).

Cách xác thực hoạt động dưới mui xe?

Dưới nắp, trình xác thực được áp dụng bằng chỉ thị (check the source code). Các chỉ thị có providers: [REQUIRED_VALIDATOR] có nghĩa là bộ cấp bậc riêng được sử dụng để đăng ký cá thể trình xác nhận hợp lệ đó. Vì vậy, tùy thuộc vào các thuộc tính được áp dụng trên phần tử, các chỉ thị sẽ thêm các trường hợp trình xác nhận hợp lệ trên bộ phun được liên kết với phần tử đích.

Tiếp theo, các trình xác thực này được truy xuất theo số NgModelFormControlDirective.

Validators cũng như accessors giá trị được lấy ra như:

constructor(@Optional() @Host() parent: ControlContainer, 
       @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, 
       @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>, 
       @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) 

và tương ứng:

constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, 
       @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>, 
       @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) 
       valueAccessors: ControlValueAccessor[]) 

Lưu ý rằng @Self() được sử dụng, phun do đó riêng (của phần tử mà chỉ thị đang được áp dụng) được sử dụng để có được các phụ thuộc.

NgModelFormControlDirective có phiên bản FormControl thực sự cập nhật giá trị và thực thi trình xác thực.

Do đó, điểm chính để tương tác với trường hợp là FormControl.

Ngoài ra, tất cả trình xác thực hoặc giá trị truy cập được đăng ký trong bộ phun của phần tử mà chúng được áp dụng. Điều này có nghĩa là phụ huynh không nên truy cập vào bộ phun đó. Vì vậy, sẽ là một thực tế xấu để truy cập từ thành phần hiện tại các vòi phun được cung cấp bởi các <select>.

Mẫu mã cho Lựa chọn 1 (dễ dàng thay thế bằng cách lựa chọn 2)

Các mẫu sau đây có hai xác nhận: một trong đó là cần thiết và một trong đó là một mô hình mà buộc tùy chọn để phù hợp với "tùy chọn 3".

The PLUNKER

options.component.ts

import {AfterViewInit, Component, forwardRef, Input, OnInit, ViewChild} from '@angular/core'; 
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, NgModel} from '@angular/forms'; 
import {SettingsService} from '../settings.service'; 

const OPTIONS_VALUE_ACCESSOR: any = { 
    multi: true, 
    provide: NG_VALUE_ACCESSOR, 
    useExisting: forwardRef(() => OptionsComponent) 
}; 

@Component({ 
    providers: [OPTIONS_VALUE_ACCESSOR], 
    selector: 'inf-select[name]', 
    templateUrl: './options.component.html', 
    styleUrls: ['./options.component.scss'] 
}) 
export class OptionsComponent implements ControlValueAccessor, OnInit, AfterViewInit { 

    @ViewChild('selectModel') selectModel: NgModel; 
    @Input() formControl: FormControl; 

    @Input() name: string; 
    @Input() disabled = false; 

    private propagateChange: Function; 
    private onTouched: Function; 

    private settingsService: SettingsService; 

    selectedValue: any; 

    constructor(settingsService: SettingsService) { 
    this.settingsService = settingsService; 
    } 

    ngOnInit(): void { 
    if (!this.name) { 
     throw new Error('Option name is required. eg.: <options [name]="myOption"></options>>'); 
    } 
    } 

    ngAfterViewInit(): void { 
    this.selectModel.control.valueChanges.subscribe(() => { 
     this.selectModel.control.setErrors(this.formControl.errors); 
    }); 
    } 

    writeValue(obj: any): void { 
    this.selectedValue = obj; 
    } 

    registerOnChange(fn: any): void { 
    this.propagateChange = fn; 
    } 

    registerOnTouched(fn: any): void { 
    this.onTouched = fn; 
    } 

    setDisabledState(isDisabled: boolean): void { 
    this.disabled = isDisabled; 
    } 
} 

options.component.html

<select #selectModel="ngModel" 
     class="form-control" 
     [disabled]="disabled" 
     [(ngModel)]="selectedValue" 
     (ngModelChange)="propagateChange($event)"> 
    <option value="">Select an option</option> 
    <option *ngFor="let option of settingsService.getOption(name)" [value]="option.description"> 
    {{option.description}} 
    </option> 
</select> 

options.component.SCSS

:host { 
    display: inline-block; 
    border: 5px solid transparent; 

    &.ng-invalid { 
    border-color: purple; 
    } 

    select { 
    border: 5px solid transparent; 

    &.ng-invalid { 
     border-color: red; 
    } 
    } 
} 

Cách sử dụng

Xác định FormControl dụ:

export class AppComponent implements OnInit { 

    public control: FormControl; 

    constructor() { 
    this.control = new FormControl('', Validators.compose([Validators.pattern(/^option 3$/), Validators.required])); 
    } 
... 

Bind các FormControl dụ để các thành phần:

<inf-select name="myName" [formControl]="control"></inf-select> 

Cài đặt DummyService

/** 
* TODO remove this class, added just to make injection work 
*/ 
export class SettingsService { 

    public getOption(name: string): [{ description: string }] { 
    return [ 
     { description: 'option 1' }, 
     { description: 'option 2' }, 
     { description: 'option 3' }, 
     { description: 'option 4' }, 
     { description: 'option 5' }, 
    ]; 
    } 
} 
+0

Xin chào! Cảm ơn bạn vì câu trả lời. Tuy nhiên, làm thế nào để bạn truy cập 'FormControl' khi form được xây dựng thông qua FormBuilder? Trong ví dụ của bạn thành phần sẽ được gọi theo cách này: ''. –

+0

@SlavaFominII giả sử bạn tạo một 'FormGroup' bằng cách sử dụng' FormBuilder'. Sau đó, bạn có thể nhận được sự kiểm soát bởi 'formGroup.controls ['someControl']' hoặc bằng 'formGroup.get ('someControl')'. – andreim

+0

Vâng, tôi biết điều đó, cảm ơn bạn. Tôi đã làm rõ câu hỏi của mình ở đây: https://stackoverflow.com/questions/44731894/get-access-to-formcontrol-from-the-custom-form-component-in-angular –

Các vấn đề liên quan